From 1fb695f2ca78ecf37a5f999fec88e628e64ed67e Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Tue, 10 Oct 2023 16:14:21 +0200 Subject: [PATCH 01/41] WIP for .NET 4.8 support --- Directory.Build.props | 7 +- ...tore.Client.PersistentSubscriptions.csproj | 5 + ...ntStoreProjectionManagementClient.State.cs | 25 +++- .../EventStore.Client.Streams.csproj | 8 ++ .../EventStore.Client.csproj | 6 + src/EventStore.Client/HttpFallback.cs | 4 + src/EventStore.Client/Position.cs | 4 +- src/EventStore.Client/Shims/Index.cs | 129 ++++++++++++++++++ src/EventStore.Client/Shims/IsExternalInit.cs | 9 ++ src/EventStore.Client/Shims/Range.cs | 78 +++++++++++ src/EventStore.Client/StreamIdentifier.cs | 4 + 11 files changed, 269 insertions(+), 10 deletions(-) create mode 100644 src/EventStore.Client/Shims/Index.cs create mode 100644 src/EventStore.Client/Shims/IsExternalInit.cs create mode 100644 src/EventStore.Client/Shims/Range.cs diff --git a/Directory.Build.props b/Directory.Build.props index 20ce0fd68..1f72e165f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,6 @@ - net5.0;net6.0;net7.0 - x64 + net5.0;net6.0;net7.0;net48 true enable enable @@ -16,4 +15,8 @@ 2.49.0 2.50.0 + + + + diff --git a/src/EventStore.Client.PersistentSubscriptions/EventStore.Client.PersistentSubscriptions.csproj b/src/EventStore.Client.PersistentSubscriptions/EventStore.Client.PersistentSubscriptions.csproj index 855b8780f..f1998421e 100644 --- a/src/EventStore.Client.PersistentSubscriptions/EventStore.Client.PersistentSubscriptions.csproj +++ b/src/EventStore.Client.PersistentSubscriptions/EventStore.Client.PersistentSubscriptions.csproj @@ -3,4 +3,9 @@ The GRPC client API for Event Store Persistent Subscriptions. Get the open source or commercial versions of Event Store server from https://eventstore.com/ + + + IsExternalInit.cs + + diff --git a/src/EventStore.Client.ProjectionManagement/EventStoreProjectionManagementClient.State.cs b/src/EventStore.Client.ProjectionManagement/EventStoreProjectionManagementClient.State.cs index f1c462fcd..cb58f5321 100644 --- a/src/EventStore.Client.ProjectionManagement/EventStoreProjectionManagementClient.State.cs +++ b/src/EventStore.Client.ProjectionManagement/EventStoreProjectionManagementClient.State.cs @@ -23,8 +23,11 @@ public async Task GetResultAsync(string name, string? partition = CancellationToken cancellationToken = default) { var value = await GetResultInternalAsync(name, partition, deadline, userCredentials, cancellationToken) .ConfigureAwait(false); - +#if NET await using var stream = new MemoryStream(); +#else + using var stream = new MemoryStream(); +#endif await using var writer = new Utf8JsonWriter(stream); var serializer = new ValueSerializer(); serializer.Write(writer, value, new JsonSerializerOptions()); @@ -51,7 +54,11 @@ public async Task GetResultAsync(string name, string? partition = null, CancellationToken cancellationToken = default) { var value = await GetResultInternalAsync(name, partition, deadline, userCredentials, cancellationToken) .ConfigureAwait(false); +#if NET await using var stream = new MemoryStream(); +#else + using var stream = new MemoryStream(); +#endif await using var writer = new Utf8JsonWriter(stream); var serializer = new ValueSerializer(); serializer.Write(writer, value, new JsonSerializerOptions()); @@ -90,8 +97,11 @@ public async Task GetStateAsync(string name, string? partition = n CancellationToken cancellationToken = default) { var value = await GetStateInternalAsync(name, partition, deadline, userCredentials, cancellationToken) .ConfigureAwait(false); - +#if NET await using var stream = new MemoryStream(); +#else + using var stream = new MemoryStream(); +#endif await using var writer = new Utf8JsonWriter(stream); var serializer = new ValueSerializer(); serializer.Write(writer, value, new JsonSerializerOptions()); @@ -117,8 +127,11 @@ public async Task GetStateAsync(string name, string? partition = null, UserCredentials? userCredentials = null, CancellationToken cancellationToken = default) { var value = await GetStateInternalAsync(name, partition, deadline, userCredentials, cancellationToken) .ConfigureAwait(false); - +#if NET await using var stream = new MemoryStream(); +#else + using var stream = new MemoryStream(); +#endif await using var writer = new Utf8JsonWriter(stream); var serializer = new ValueSerializer(); serializer.Write(writer, value, new JsonSerializerOptions()); @@ -173,9 +186,9 @@ public override void Write(Utf8JsonWriter writer, Value value, JsonSerializerOpt break; case Value.KindOneofCase.StructValue: writer.WriteStartObject(); - foreach (var (name, item) in value.StructValue.Fields) { - writer.WritePropertyName(name); - Write(writer, item, options); + foreach (var map in value.StructValue.Fields) { + writer.WritePropertyName(map.Key); + Write(writer, map.Value, options); } writer.WriteEndObject(); diff --git a/src/EventStore.Client.Streams/EventStore.Client.Streams.csproj b/src/EventStore.Client.Streams/EventStore.Client.Streams.csproj index 430821e42..d40bde8cd 100644 --- a/src/EventStore.Client.Streams/EventStore.Client.Streams.csproj +++ b/src/EventStore.Client.Streams/EventStore.Client.Streams.csproj @@ -3,4 +3,12 @@ The GRPC client API for Event Store Streams. Get the open source or commercial versions of Event Store server from https://eventstore.com/ + + + + + + IsExternalInit.cs + + diff --git a/src/EventStore.Client/EventStore.Client.csproj b/src/EventStore.Client/EventStore.Client.csproj index 32db2e4d7..1933aaa5a 100644 --- a/src/EventStore.Client/EventStore.Client.csproj +++ b/src/EventStore.Client/EventStore.Client.csproj @@ -21,4 +21,10 @@ + + + + + + diff --git a/src/EventStore.Client/HttpFallback.cs b/src/EventStore.Client/HttpFallback.cs index 2a734a1c0..c1b213591 100644 --- a/src/EventStore.Client/HttpFallback.cs +++ b/src/EventStore.Client/HttpFallback.cs @@ -39,7 +39,11 @@ internal async Task HttpGetAsync(string path, ChannelInfo channelInfo, Tim var httpResult = await HttpSendAsync(request, onNotFound, deadline, cancellationToken).ConfigureAwait(false); +#if NET var json = await httpResult.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); +#else + var json = await httpResult.Content.ReadAsStringAsync().ConfigureAwait(false); +#endif var result = JsonSerializer.Deserialize(json, _jsonSettings); if (result == null) { diff --git a/src/EventStore.Client/Position.cs b/src/EventStore.Client/Position.cs index 71fb54ef8..169439804 100644 --- a/src/EventStore.Client/Position.cs +++ b/src/EventStore.Client/Position.cs @@ -164,7 +164,7 @@ public bool Equals(Position other) => /// true if the value was converted successfully; otherwise, false. public static bool TryParse(string value, out Position? position) { position = null; - var parts = value.Split("/"); + var parts = value.Split('/'); if (parts.Length != 2) { return false; @@ -184,7 +184,7 @@ public static bool TryParse(string value, out Position? position) { static bool TryParsePosition(string expectedPrefix, string v, out ulong p) { p = 0; - var prts = v.Split(":"); + var prts = v.Split(':'); if (prts.Length != 2) { return false; } diff --git a/src/EventStore.Client/Shims/Index.cs b/src/EventStore.Client/Shims/Index.cs new file mode 100644 index 000000000..9d0bcbd7b --- /dev/null +++ b/src/EventStore.Client/Shims/Index.cs @@ -0,0 +1,129 @@ +using System.Runtime.CompilerServices; + +namespace System; + +/// Represent a type can be used to index a collection either from the start or the end. +/// +/// Index is used by the C# compiler to support the new index syntax +/// +/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ; +/// int lastElement = someArray[^1]; // lastElement = 5 +/// +/// +public readonly struct Index : IEquatable +{ + private readonly int _value; + + /// Construct an Index using a value and indicating if the index is from the start or from the end. + /// The index value. it has to be zero or positive number. + /// Indicating if the index is from the start or from the end. + /// + /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Index(int value, bool fromEnd = false) + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); + } + + if (fromEnd) + _value = ~value; + else + _value = value; + } + + // The following private constructors mainly created for perf reason to avoid the checks + private Index(int value) + { + _value = value; + } + + /// Create an Index pointing at first element. + public static Index Start => new Index(0); + + /// Create an Index pointing at beyond last element. + public static Index End => new Index(~0); + + /// Create an Index from the start at the position indicated by the value. + /// The index value from the start. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Index FromStart(int value) + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); + } + + return new Index(value); + } + + /// Create an Index from the end at the position indicated by the value. + /// The index value from the end. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Index FromEnd(int value) + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); + } + + return new Index(~value); + } + + /// Returns the index value. + public int Value + { + get + { + if (_value < 0) + return ~_value; + else + return _value; + } + } + + /// Indicates whether the index is from the start or the end. + public bool IsFromEnd => _value < 0; + + /// Calculate the offset from the start using the giving collection length. + /// The length of the collection that the Index will be used with. length has to be a positive value + /// + /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values. + /// we don't validate either the returned offset is greater than the input length. + /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and + /// then used to index a collection will get out of range exception which will be same affect as the validation. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetOffset(int length) + { + int offset = _value; + if (IsFromEnd) + { + // offset = length - (~value) + // offset = length + (~(~value) + 1) + // offset = length + value + 1 + + offset += length + 1; + } + return offset; + } + + /// Indicates whether the current Index object is equal to another object of the same type. + /// An object to compare with this object + public override bool Equals(object? value) => value is Index && _value == ((Index)value)._value; + + /// Indicates whether the current Index object is equal to another Index object. + /// An object to compare with this object + public bool Equals(Index other) => _value == other._value; + + /// Returns the hash code for this instance. + public override int GetHashCode() => _value; + + /// Converts integer number to an Index. + public static implicit operator Index(int value) => FromStart(value); + + /// Converts the value of the current Index object to its equivalent string representation. + public override string ToString() => IsFromEnd ? $"^{((uint)Value)}" : ((uint)Value).ToString(); +} diff --git a/src/EventStore.Client/Shims/IsExternalInit.cs b/src/EventStore.Client/Shims/IsExternalInit.cs new file mode 100644 index 000000000..a77ccc3c3 --- /dev/null +++ b/src/EventStore.Client/Shims/IsExternalInit.cs @@ -0,0 +1,9 @@ +#if !NET +using System.ComponentModel; + +// ReSharper disable once CheckNamespace +namespace System.Runtime.CompilerServices; + +[EditorBrowsable(EditorBrowsableState.Never)] +internal class IsExternalInit{} +#endif diff --git a/src/EventStore.Client/Shims/Range.cs b/src/EventStore.Client/Shims/Range.cs new file mode 100644 index 000000000..4eb494b94 --- /dev/null +++ b/src/EventStore.Client/Shims/Range.cs @@ -0,0 +1,78 @@ +using System.Runtime.CompilerServices; + +namespace System; + +/// Represent a range has start and end indexes. +/// +/// Range is used by the C# compiler to support the range syntax. +/// +/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 }; +/// int[] subArray1 = someArray[0..2]; // { 1, 2 } +/// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 } +/// +/// +public readonly struct Range : IEquatable +{ + /// Represent the inclusive start index of the Range. + public Index Start { get; } + + /// Represent the exclusive end index of the Range. + public Index End { get; } + + /// Construct a Range object using the start and end indexes. + /// Represent the inclusive start index of the range. + /// Represent the exclusive end index of the range. + public Range(Index start, Index end) + { + Start = start; + End = end; + } + + /// Indicates whether the current Range object is equal to another object of the same type. + /// An object to compare with this object + public override bool Equals(object? value) => + value is Range r && + r.Start.Equals(Start) && + r.End.Equals(End); + + /// Indicates whether the current Range object is equal to another Range object. + /// An object to compare with this object + public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End); + + /// Returns the hash code for this instance. + public override int GetHashCode() => Start.GetHashCode() * 31 + End.GetHashCode(); + + /// Converts the value of the current Range object to its equivalent string representation. + public override string ToString() => $"{Start}..{End}"; + + /// Create a Range object starting from start index to the end of the collection. + public static Range StartAt(Index start) => new(start, Index.End); + + /// Create a Range object starting from first element in the collection to the end Index. + public static Range EndAt(Index end) => new(Index.Start, end); + + /// Create a Range object starting from first element to the end. + public static Range All => new(Index.Start, Index.End); + + /// Calculate the start offset and length of range object using a collection length. + /// The length of the collection that the range will be used with. length has to be a positive value. + /// + /// For performance reason, we don't validate the input length parameter against negative values. + /// It is expected Range will be used with collections which always have non negative length/count. + /// We validate the range is inside the length scope though. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [CLSCompliant(false)] + public (int Offset, int Length) GetOffsetAndLength(int length) + { + int start = Start.GetOffset(length); + int end = End.GetOffset(length); + + if ((uint)end > (uint)length || (uint)start > (uint)end) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + return (start, end - start); + } +} diff --git a/src/EventStore.Client/StreamIdentifier.cs b/src/EventStore.Client/StreamIdentifier.cs index fd0ed8d4f..73e0ee25e 100644 --- a/src/EventStore.Client/StreamIdentifier.cs +++ b/src/EventStore.Client/StreamIdentifier.cs @@ -12,7 +12,11 @@ public partial class StreamIdentifier { } if (source._cached != null || source.StreamName.IsEmpty) return source._cached; +#if NET var tmp = Encoding.UTF8.GetString(source.StreamName.Span); +#else + var tmp = Encoding.UTF8.GetString(source.StreamName.ToByteArray()); +#endif //this doesn't have to be thread safe, its just a cache in case the identifier is turned into a string several times source._cached = tmp; return source._cached; From af9944ca905bbb7c48e9e407e2f36d48e88905ac Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Wed, 11 Oct 2023 12:04:16 +0200 Subject: [PATCH 02/41] Make it build --- .editorconfig | 30 ++++++---- Directory.Build.props | 6 +- src/Directory.Build.props | 19 ++----- .../EpochExtensions.cs | 14 ++++- .../Shims/Index.cs | 4 +- .../Shims/IsExternalInit.cs | 0 .../Shims/Range.cs | 5 +- .../Shims/TaskCompletionSource.cs | 8 +++ ...tore.Client.PersistentSubscriptions.csproj | 4 +- .../EventStore.Client.Streams.csproj | 5 +- .../EventStoreClient.Append.cs | 18 +++++- .../EventStoreClient.Read.cs | 8 +-- src/EventStore.Client/ChannelFactory.cs | 13 ++++- .../EventStore.Client.csproj | 8 ++- ...entStoreClientSettings.ConnectionString.cs | 46 +++++++++------ src/EventStore.Client/Uuid.cs | 57 +++++++++++++++---- test/Directory.Build.props | 8 +-- ...lient.PersistentSubscriptions.Tests.csproj | 3 + .../SubscriptionToAll/get_info.cs | 18 +++--- .../SubscriptionToStream/get_info.cs | 18 +++--- .../EventStore.Client.Streams.Tests.csproj | 3 + .../EventStoreClientFixtureBase.cs | 3 - .../EventStoreTestServer.cs | 14 ++++- .../EventStoreTestServerCluster.cs | 8 +++ .../GlobalEnvironment.cs | 8 +-- .../ConnectionStringTests.cs | 30 ++++++++-- .../EventStore.Client.Tests.csproj | 16 +++--- .../GrpcServerCapabilitiesClientTests.cs | 2 + 28 files changed, 252 insertions(+), 124 deletions(-) rename src/{EventStore.Client => EventStore.Client.Common}/Shims/Index.cs (98%) rename src/{EventStore.Client => EventStore.Client.Common}/Shims/IsExternalInit.cs (100%) rename src/{EventStore.Client => EventStore.Client.Common}/Shims/Range.cs (97%) create mode 100644 src/EventStore.Client.Common/Shims/TaskCompletionSource.cs diff --git a/.editorconfig b/.editorconfig index 1a4fb776a..56c3583ce 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,8 +1,8 @@ +root = true # EditorConfig is awesome: http://EditorConfig.org # top-most EditorConfig file -root = true [*] insert_final_newline = true @@ -44,33 +44,33 @@ dotnet_style_predefined_type_for_member_access = true:suggestion # name all constant fields using PascalCase dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion -dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields -dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style -dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.applicable_kinds = field dotnet_naming_symbols.constant_fields.required_modifiers = const dotnet_naming_style.pascal_case_style.capitalization = pascal_case # static fields should have s_ prefix dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion -dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields +dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields -dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.applicable_kinds = field dotnet_naming_symbols.static_fields.required_modifiers = static -dotnet_naming_style.static_prefix_style.capitalization = pascal_case +dotnet_naming_style.static_prefix_style.capitalization = pascal_case # internal and private fields should be _camelCase dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion -dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields -dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style +dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields +dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style dotnet_naming_symbols.private_internal_fields.applicable_kinds = field dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal dotnet_naming_style.camel_case_underscore_style.required_prefix = _ -dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case +dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case # Code style defaults dotnet_sort_system_directives_first = true @@ -124,6 +124,16 @@ csharp_space_between_method_declaration_name_and_open_parenthesis = false csharp_space_between_method_declaration_parameter_list_parentheses = false csharp_space_between_parentheses = false csharp_space_between_square_brackets = false +indent_style = space +indent_size = 4 +tab_width = 4 + +# ReSharper properties +resharper_csharp_indent_style = space +resharper_default_private_modifier = explicit +resharper_default_internal_modifier = explicit +resharper_int_align_binary_expressions = false +resharper_int_align_fields = false [*.{asm,inc}] indent_size = 8 diff --git a/Directory.Build.props b/Directory.Build.props index 1f72e165f..37918c2f4 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - net5.0;net6.0;net7.0;net48 + net6.0;net7.0;net48 true enable enable @@ -12,8 +12,8 @@ $(MSBuildThisFileDirectory)\bin\$(Configuration)\$(MSBuildProjectName)\ EventStore.Client true - 2.49.0 - 2.50.0 + 2.57.0 + 2.58.0 diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 1844c3399..ffe2a4475 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -22,7 +22,7 @@ https://eventstore.com/blog/ eventstore client grpc Event Store Ltd - Copyright 2012-2020 Event Store Ltd + Copyright 2012-2023 Event Store Ltd v true @@ -31,22 +31,11 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers - + + - - - - - <_Parameter1>$(ProjectName).Tests - + diff --git a/src/EventStore.Client.Common/EpochExtensions.cs b/src/EventStore.Client.Common/EpochExtensions.cs index 5a9875153..e4d84dede 100644 --- a/src/EventStore.Client.Common/EpochExtensions.cs +++ b/src/EventStore.Client.Common/EpochExtensions.cs @@ -2,7 +2,19 @@ namespace EventStore.Client { internal static class EpochExtensions { - private static readonly DateTime UnixEpoch = DateTime.UnixEpoch; + private const long TicksPerMillisecond = 10000; + private const long TicksPerSecond = TicksPerMillisecond * 1000; + private const long TicksPerMinute = TicksPerSecond * 60; + private const long TicksPerHour = TicksPerMinute * 60; + private const long TicksPerDay = TicksPerHour * 24; + private const int DaysPerYear = 365; + private const int DaysPer4Years = DaysPerYear * 4 + 1; + private const int DaysPer100Years = DaysPer4Years * 25 - 1; + private const int DaysPer400Years = DaysPer100Years * 4 + 1; + private const int DaysTo1970 = DaysPer400Years * 4 + DaysPer100Years * 3 + DaysPer4Years * 17 + DaysPerYear; + private const long UnixEpochTicks = DaysTo1970 * TicksPerDay; + + private static readonly DateTime UnixEpoch = new(UnixEpochTicks, DateTimeKind.Utc); public static DateTime FromTicksSinceEpoch(this long value) => new DateTime(UnixEpoch.Ticks + value, DateTimeKind.Utc); diff --git a/src/EventStore.Client/Shims/Index.cs b/src/EventStore.Client.Common/Shims/Index.cs similarity index 98% rename from src/EventStore.Client/Shims/Index.cs rename to src/EventStore.Client.Common/Shims/Index.cs index 9d0bcbd7b..67af4a05d 100644 --- a/src/EventStore.Client/Shims/Index.cs +++ b/src/EventStore.Client.Common/Shims/Index.cs @@ -1,3 +1,4 @@ +#if !NET using System.Runtime.CompilerServices; namespace System; @@ -10,7 +11,7 @@ namespace System; /// int lastElement = someArray[^1]; // lastElement = 5 /// /// -public readonly struct Index : IEquatable +internal readonly struct Index : IEquatable { private readonly int _value; @@ -127,3 +128,4 @@ public int GetOffset(int length) /// Converts the value of the current Index object to its equivalent string representation. public override string ToString() => IsFromEnd ? $"^{((uint)Value)}" : ((uint)Value).ToString(); } +#endif diff --git a/src/EventStore.Client/Shims/IsExternalInit.cs b/src/EventStore.Client.Common/Shims/IsExternalInit.cs similarity index 100% rename from src/EventStore.Client/Shims/IsExternalInit.cs rename to src/EventStore.Client.Common/Shims/IsExternalInit.cs diff --git a/src/EventStore.Client/Shims/Range.cs b/src/EventStore.Client.Common/Shims/Range.cs similarity index 97% rename from src/EventStore.Client/Shims/Range.cs rename to src/EventStore.Client.Common/Shims/Range.cs index 4eb494b94..9d4b88f2f 100644 --- a/src/EventStore.Client/Shims/Range.cs +++ b/src/EventStore.Client.Common/Shims/Range.cs @@ -1,3 +1,4 @@ +#if !NET using System.Runtime.CompilerServices; namespace System; @@ -11,7 +12,7 @@ namespace System; /// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 } /// /// -public readonly struct Range : IEquatable +internal readonly struct Range : IEquatable { /// Represent the inclusive start index of the Range. public Index Start { get; } @@ -62,7 +63,6 @@ value is Range r && /// We validate the range is inside the length scope though. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [CLSCompliant(false)] public (int Offset, int Length) GetOffsetAndLength(int length) { int start = Start.GetOffset(length); @@ -76,3 +76,4 @@ value is Range r && return (start, end - start); } } +#endif diff --git a/src/EventStore.Client.Common/Shims/TaskCompletionSource.cs b/src/EventStore.Client.Common/Shims/TaskCompletionSource.cs new file mode 100644 index 000000000..5e23aa609 --- /dev/null +++ b/src/EventStore.Client.Common/Shims/TaskCompletionSource.cs @@ -0,0 +1,8 @@ +#if !NET +namespace System.Threading.Tasks; + +internal class TaskCompletionSource : TaskCompletionSource { + public void SetResult() => base.SetResult(null); + public bool TrySetResult() => base.TrySetResult(null); +} +#endif diff --git a/src/EventStore.Client.PersistentSubscriptions/EventStore.Client.PersistentSubscriptions.csproj b/src/EventStore.Client.PersistentSubscriptions/EventStore.Client.PersistentSubscriptions.csproj index f1998421e..8d6521549 100644 --- a/src/EventStore.Client.PersistentSubscriptions/EventStore.Client.PersistentSubscriptions.csproj +++ b/src/EventStore.Client.PersistentSubscriptions/EventStore.Client.PersistentSubscriptions.csproj @@ -4,8 +4,6 @@ The GRPC client API for Event Store Persistent Subscriptions. Get the open source or commercial versions of Event Store server from https://eventstore.com/ - - IsExternalInit.cs - + diff --git a/src/EventStore.Client.Streams/EventStore.Client.Streams.csproj b/src/EventStore.Client.Streams/EventStore.Client.Streams.csproj index d40bde8cd..1dc68f255 100644 --- a/src/EventStore.Client.Streams/EventStore.Client.Streams.csproj +++ b/src/EventStore.Client.Streams/EventStore.Client.Streams.csproj @@ -5,10 +5,9 @@ + - - IsExternalInit.cs - + diff --git a/src/EventStore.Client.Streams/EventStoreClient.Append.cs b/src/EventStore.Client.Streams/EventStoreClient.Append.cs index a780f1e00..a0e73c6e5 100644 --- a/src/EventStore.Client.Streams/EventStoreClient.Append.cs +++ b/src/EventStore.Client.Streams/EventStoreClient.Append.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using System.Threading.Channels; @@ -257,8 +258,8 @@ private async Task Receive() { // complete whatever tcs's we have _onException(ex); - foreach (var (_, source) in _pendingRequests) { - source.TrySetException(ex); + foreach (var request in _pendingRequests) { + request.Value.TrySetException(ex); } } } @@ -268,7 +269,7 @@ private async Task Send() { if (call is null) throw new NotSupportedException("Server does not support batch append"); - await foreach (var appendRequest in _channel.Reader.ReadAllAsync(_cancellationToken) + await foreach (var appendRequest in ReadAllAsync(_channel.Reader, _cancellationToken) .ConfigureAwait(false)) { await call.RequestStream.WriteAsync(appendRequest).ConfigureAwait(false); } @@ -339,5 +340,16 @@ public void Dispose() { _channel.Writer.TryComplete(); } } + + private static async IAsyncEnumerable ReadAllAsync(ChannelReader reader, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + while (await reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + { + while (reader.TryRead(out T? item)) + { + yield return item; + } + } + } } } diff --git a/src/EventStore.Client.Streams/EventStoreClient.Read.cs b/src/EventStore.Client.Streams/EventStoreClient.Read.cs index 25635f26a..39cc588d9 100644 --- a/src/EventStore.Client.Streams/EventStoreClient.Read.cs +++ b/src/EventStore.Client.Streams/EventStoreClient.Read.cs @@ -87,7 +87,7 @@ async IAsyncEnumerable GetMessages() { } try { - await foreach (var message in _channel.Reader.ReadAllAsync().ConfigureAwait(false)) { + await foreach (var message in ReadAllAsync(_channel.Reader).ConfigureAwait(false)) { if (message is StreamMessage.LastAllStreamPosition(var position)) { LastPosition = position; } @@ -151,7 +151,7 @@ public async IAsyncEnumerator GetAsyncEnumerator( CancellationToken cancellationToken = default) { try { - await foreach (var message in _channel.Reader.ReadAllAsync(cancellationToken).ConfigureAwait(false)) { + await foreach (var message in ReadAllAsync(_channel.Reader, cancellationToken).ConfigureAwait(false)) { if (message is not StreamMessage.Event e) { continue; } @@ -250,7 +250,7 @@ async IAsyncEnumerable GetMessages() { } try { - await foreach (var message in _channel.Reader.ReadAllAsync().ConfigureAwait(false)) { + await foreach (var message in ReadAllAsync(_channel.Reader).ConfigureAwait(false)) { switch (message) { case StreamMessage.FirstStreamPosition(var streamPosition): FirstStreamPosition = streamPosition; @@ -348,7 +348,7 @@ public async IAsyncEnumerator GetAsyncEnumerator( CancellationToken cancellationToken = default) { try { - await foreach (var message in _channel.Reader.ReadAllAsync(cancellationToken).ConfigureAwait(false)) { + await foreach (var message in ReadAllAsync(_channel.Reader, cancellationToken).ConfigureAwait(false)) { if (message is StreamMessage.NotFound) { throw new StreamNotFoundException(StreamName); } diff --git a/src/EventStore.Client/ChannelFactory.cs b/src/EventStore.Client/ChannelFactory.cs index 06d2888de..f920fde39 100644 --- a/src/EventStore.Client/ChannelFactory.cs +++ b/src/EventStore.Client/ChannelFactory.cs @@ -19,7 +19,9 @@ public static TChannel CreateChannel(EventStoreClientSettings settings, EndPoint return TChannel.ForAddress(address, new GrpcChannelOptions { HttpClient = new HttpClient(CreateHandler(), true) { Timeout = System.Threading.Timeout.InfiniteTimeSpan, +#if NET DefaultRequestVersion = new Version(2, 0), +#endif }, LoggerFactory = settings.LoggerFactory, Credentials = settings.ChannelCredentials, @@ -32,12 +34,21 @@ HttpMessageHandler CreateHandler() { return settings.CreateHttpMessageHandler.Invoke(); } +#if NET return new SocketsHttpHandler { KeepAlivePingDelay = settings.ConnectivitySettings.KeepAliveInterval, KeepAlivePingTimeout = settings.ConnectivitySettings.KeepAliveTimeout, EnableMultipleHttp2Connections = true }; - } +#else + return new WinHttpHandler { + TcpKeepAliveEnabled = true, + TcpKeepAliveTime = settings.ConnectivitySettings.KeepAliveTimeout, + TcpKeepAliveInterval = settings.ConnectivitySettings.KeepAliveInterval, + EnableMultipleHttp2Connections = true + }; +#endif + } } } } diff --git a/src/EventStore.Client/EventStore.Client.csproj b/src/EventStore.Client/EventStore.Client.csproj index 1933aaa5a..44b539d2d 100644 --- a/src/EventStore.Client/EventStore.Client.csproj +++ b/src/EventStore.Client/EventStore.Client.csproj @@ -6,11 +6,12 @@ EventStore.Client.Grpc - + + @@ -25,6 +26,9 @@ - + + + + diff --git a/src/EventStore.Client/EventStoreClientSettings.ConnectionString.cs b/src/EventStore.Client/EventStoreClientSettings.ConnectionString.cs index 76b3c9c6f..d65b45030 100644 --- a/src/EventStore.Client/EventStoreClientSettings.ConnectionString.cs +++ b/src/EventStore.Client/EventStoreClientSettings.ConnectionString.cs @@ -116,19 +116,19 @@ private static EventStoreClientSettings CreateSettings(string scheme, (string us settings.DefaultCredentials = new UserCredentials(userInfo.Value.user, userInfo.Value.pass); var typedOptions = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - foreach (var (key, value) in options) { - if (!SettingsType.TryGetValue(key, out var type)) - throw new InvalidSettingException($"Unknown option: {key}"); + foreach (var kv in options) { + if (!SettingsType.TryGetValue(kv.Key, out var type)) + throw new InvalidSettingException($"Unknown option: {kv.Key}"); if (type == typeof(int)) { - if (!int.TryParse(value, out var intValue)) - throw new InvalidSettingException($"{key} must be an integer value"); - typedOptions.Add(key, intValue); + if (!int.TryParse(kv.Value, out var intValue)) + throw new InvalidSettingException($"{kv.Key} must be an integer value"); + typedOptions.Add(kv.Key, intValue); } else if (type == typeof(bool)) { - if (!bool.TryParse(value, out var boolValue)) - throw new InvalidSettingException($"{key} must be either true or false"); - typedOptions.Add(key, boolValue); + if (!bool.TryParse(kv.Value, out var boolValue)) + throw new InvalidSettingException($"{kv.Key} must be either true or false"); + typedOptions.Add(kv.Key, boolValue); } else if (type == typeof(string)) { - typedOptions.Add(key, value); + typedOptions.Add(kv.Key, kv.Value); } } @@ -198,8 +198,13 @@ private static EventStoreClientSettings CreateSettings(string scheme, (string us if (typedOptions.TryGetValue(TlsVerifyCert, out var tlsVerifyCert)) { settings.ConnectivitySettings.TlsVerifyCert = (bool)tlsVerifyCert; } - - settings.CreateHttpMessageHandler = () => { + + settings.CreateHttpMessageHandler = CreateDefaultHandler; + + return settings; + + HttpMessageHandler CreateDefaultHandler() { +#if NET var handler = new SocketsHttpHandler { KeepAlivePingDelay = settings.ConnectivitySettings.KeepAliveInterval, KeepAlivePingTimeout = settings.ConnectivitySettings.KeepAliveTimeout, @@ -209,11 +214,20 @@ private static EventStoreClientSettings CreateSettings(string scheme, (string us if (!settings.ConnectivitySettings.TlsVerifyCert) { handler.SslOptions.RemoteCertificateValidationCallback = delegate { return true; }; } - +#else + var handler = new WinHttpHandler { + TcpKeepAliveEnabled = true, + TcpKeepAliveTime = settings.ConnectivitySettings.KeepAliveTimeout, + TcpKeepAliveInterval = settings.ConnectivitySettings.KeepAliveInterval, + EnableMultipleHttp2Connections = true + }; + + if (!settings.ConnectivitySettings.TlsVerifyCert) { + handler.ServerCertificateValidationCallback = delegate { return true; }; + } +#endif return handler; - }; - - return settings; + } } private static string ParseScheme(string s) => diff --git a/src/EventStore.Client/Uuid.cs b/src/EventStore.Client/Uuid.cs index 98b6f9ff6..ded74ab0b 100644 --- a/src/EventStore.Client/Uuid.cs +++ b/src/EventStore.Client/Uuid.cs @@ -1,4 +1,6 @@ using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace EventStore.Client { /// @@ -66,21 +68,25 @@ private Uuid(Guid value) { Span data = stackalloc byte[16]; - if (!value.TryWriteBytes(data)) { + if (!TryWriteGuidBytes(value, data)) { throw new InvalidOperationException(); } - data.Slice(0, 8).Reverse(); - data.Slice(0, 2).Reverse(); + data[..8].Reverse(); + data[..2].Reverse(); data.Slice(2, 2).Reverse(); data.Slice(4, 4).Reverse(); - data.Slice(8).Reverse(); + data[8..].Reverse(); - _msb = BitConverter.ToInt64(data); - _lsb = BitConverter.ToInt64(data.Slice(8)); + _msb = BitConverterToInt64(data); + _lsb = BitConverterToInt64(data[8..]); } - private Uuid(string value) : this(value == null + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static long BitConverterToInt64(ReadOnlySpan value) + => Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(value)); + + private Uuid(string value) : this(value == null ? throw new ArgumentNullException(nameof(value)) : Guid.Parse(value)) { } @@ -148,18 +154,45 @@ public Guid ToGuid() { } Span data = stackalloc byte[16]; - if (!BitConverter.TryWriteBytes(data, _msb) || - !BitConverter.TryWriteBytes(data.Slice(8), _lsb)) { + if (!TryWriteBytes(data, _msb) || + !TryWriteBytes(data[8..], _lsb)) { throw new InvalidOperationException(); } - data.Slice(0, 8).Reverse(); - data.Slice(0, 4).Reverse(); + data[..8].Reverse(); + data[..4].Reverse(); data.Slice(4, 2).Reverse(); data.Slice(6, 2).Reverse(); - data.Slice(8).Reverse(); + data[8..].Reverse(); +#if NET return new Guid(data); +#else + return new Guid(data.ToArray()); +#endif } + + private static bool TryWriteBytes(Span destination, long value) + { + if (destination.Length < sizeof(long)) + return false; + + Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), value); + return true; + } + + private bool TryWriteGuidBytes(Guid value, Span destination) + { +#if NET + return value.TryWriteBytes(destination); +#else + if (destination.Length < 16) + return false; + + var bytes = value.ToByteArray(); + bytes.CopyTo(destination); + return true; +#endif + } } } diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 6c176dac1..8a6152d89 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -18,10 +18,7 @@ - - all - runtime; build; native; contentfiles; analyzers - + @@ -30,7 +27,8 @@ - + + + + + diff --git a/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/get_info.cs b/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/get_info.cs index 4d73fe56a..23d928fc7 100644 --- a/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/get_info.cs +++ b/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/get_info.cs @@ -163,15 +163,15 @@ await Client.SubscribeToAllAsync( eventAppeared: (s, e, r, ct) => { counter++; - if (counter == 1) { - s.Nack(PersistentSubscriptionNakEventAction.Park, "Test", e); - } - - if (counter > 10) { - tcs.TrySetResult(); - } - - return Task.CompletedTask; + switch (counter) { + case 1: s.Nack(PersistentSubscriptionNakEventAction.Park, "Test", e); + break; + case > 10: + tcs.TrySetResult(); + break; + } + + return Task.CompletedTask; }, userCredentials: TestCredentials.Root); diff --git a/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToStream/get_info.cs b/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToStream/get_info.cs index 66ee05f2f..59b221c08 100644 --- a/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToStream/get_info.cs +++ b/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToStream/get_info.cs @@ -152,22 +152,20 @@ protected override Task Given() => protected override async Task When() { var counter = 0; var tcs = new TaskCompletionSource(); - await Client.SubscribeToStreamAsync( StreamName, GroupName, eventAppeared: (s, e, r, ct) => { counter++; - if (counter == 1) { - s.Nack(PersistentSubscriptionNakEventAction.Park, "Test", e); - } - - if (counter > 10) { - tcs.TrySetResult(); - } - - return Task.CompletedTask; + switch (counter) { + case 1: s.Nack(PersistentSubscriptionNakEventAction.Park, "Test", e); + break; + case > 10: tcs.TrySetResult(); + break; + } + + return Task.CompletedTask; }, userCredentials: TestCredentials.Root); diff --git a/test/EventStore.Client.Streams.Tests/EventStore.Client.Streams.Tests.csproj b/test/EventStore.Client.Streams.Tests/EventStore.Client.Streams.Tests.csproj index 596039b86..34f876b5e 100644 --- a/test/EventStore.Client.Streams.Tests/EventStore.Client.Streams.Tests.csproj +++ b/test/EventStore.Client.Streams.Tests/EventStore.Client.Streams.Tests.csproj @@ -4,4 +4,7 @@ + + + diff --git a/test/EventStore.Client.Tests.Common/EventStoreClientFixtureBase.cs b/test/EventStore.Client.Tests.Common/EventStoreClientFixtureBase.cs index 754a4d29d..b966e5098 100644 --- a/test/EventStore.Client.Tests.Common/EventStoreClientFixtureBase.cs +++ b/test/EventStore.Client.Tests.Common/EventStoreClientFixtureBase.cs @@ -43,9 +43,6 @@ private static void ConfigureLogging() { .WriteTo.Observers(observable => observable.Subscribe(LogEventSubject.OnNext)) .WriteTo.Seq("http://localhost:5341/", period: TimeSpan.FromMilliseconds(1)); Log.Logger = loggerConfiguration.CreateLogger(); -#if GRPC_CORE - GrpcEnvironment.SetLogger(new GrpcCoreSerilogLogger(Log.Logger.ForContext())); -#endif AppDomain.CurrentDomain.DomainUnload += (_, e) => Log.CloseAndFlush(); } diff --git a/test/EventStore.Client.Tests.Common/EventStoreTestServer.cs b/test/EventStore.Client.Tests.Common/EventStoreTestServer.cs index 3d748abed..c6e30368b 100644 --- a/test/EventStore.Client.Tests.Common/EventStoreTestServer.cs +++ b/test/EventStore.Client.Tests.Common/EventStoreTestServer.cs @@ -51,13 +51,21 @@ public EventStoreTestServer( _hostCertificatePath = hostCertificatePath; VerifyCertificatesExist(); +#if NET _httpClient = new HttpClient(new SocketsHttpHandler { SslOptions = {RemoteCertificateValidationCallback = delegate { return true; }} }) { BaseAddress = address, }; +#else + _httpClient = new HttpClient(new WinHttpHandler { + ServerCertificateValidationCallback = delegate { return true; } + }) { + BaseAddress = address, + }; +#endif - var env = new Dictionary { + var env = new Dictionary { ["EVENTSTORE_DB_LOG_FORMAT"] = GlobalEnvironment.DbLogFormat, ["EVENTSTORE_MEM_DB"] = "true", ["EVENTSTORE_CERTIFICATE_FILE"] = "/etc/eventstore/certs/node/node.crt", @@ -67,8 +75,8 @@ public EventStoreTestServer( ["EVENTSTORE_STREAM_EXISTENCE_FILTER_SIZE"] = "10000", ["EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP"] = "True" }; - foreach (var (key, value) in envOverrides ?? Enumerable.Empty>()) { - env[key] = value; + foreach (var val in envOverrides ?? Enumerable.Empty>()) { + env[val.Key] = val.Value; } _eventStore = new Builder() diff --git a/test/EventStore.Client.Tests.Common/EventStoreTestServerCluster.cs b/test/EventStore.Client.Tests.Common/EventStoreTestServerCluster.cs index 2e84d3f1c..43dce3c2e 100644 --- a/test/EventStore.Client.Tests.Common/EventStoreTestServerCluster.cs +++ b/test/EventStore.Client.Tests.Common/EventStoreTestServerCluster.cs @@ -25,11 +25,19 @@ public EventStoreTestServerCluster( _eventStoreCluster = BuildCluster(envOverrides); +#if NET _httpClient = new HttpClient(new SocketsHttpHandler { SslOptions = {RemoteCertificateValidationCallback = delegate { return true; }} }) { BaseAddress = address, }; +#else + _httpClient = new HttpClient(new WinHttpHandler { + ServerCertificateValidationCallback = delegate { return true; } + }) { + BaseAddress = address, + }; +#endif } private ICompositeService BuildCluster(IDictionary? envOverrides = null) { diff --git a/test/EventStore.Client.Tests.Common/GlobalEnvironment.cs b/test/EventStore.Client.Tests.Common/GlobalEnvironment.cs index dfdc653e3..51042dfc2 100644 --- a/test/EventStore.Client.Tests.Common/GlobalEnvironment.cs +++ b/test/EventStore.Client.Tests.Common/GlobalEnvironment.cs @@ -28,10 +28,10 @@ public static IDictionary EnvironmentVariables(IDictionary>()) { - if (key.StartsWith("EVENTSTORE") && !_sharedEnv.Contains(key)) - throw new Exception($"Add {key} to shared.env and _sharedEnv to pass it to the cluster containers"); - env[key] = value; + foreach (var @override in overrides ?? Enumerable.Empty>()) { + if (@override.Key.StartsWith("EVENTSTORE") && !_sharedEnv.Contains(@override.Key)) + throw new Exception($"Add {@override.Key} to shared.env and _sharedEnv to pass it to the cluster containers"); + env[@override.Key] = @override.Value; } return env; } diff --git a/test/EventStore.Client.Tests/ConnectionStringTests.cs b/test/EventStore.Client.Tests/ConnectionStringTests.cs index 8ef9b5f89..2d679750a 100644 --- a/test/EventStore.Client.Tests/ConnectionStringTests.cs +++ b/test/EventStore.Client.Tests/ConnectionStringTests.cs @@ -103,12 +103,12 @@ public void valid_connection_string_with_empty_path(string connectionString, Eve Assert.Equal(expected, result, EventStoreClientSettingsEqualityComparer.Instance); } -#if !GRPC_CORE [Theory, InlineData(false), InlineData(true)] public void tls_verify_cert(bool tlsVerifyCert) { var connectionString = $"esdb://localhost:2113/?tlsVerifyCert={tlsVerifyCert}"; var result = EventStoreClientSettings.Create(connectionString); using var handler = result.CreateHttpMessageHandler?.Invoke(); +#if NET var socketsHandler = Assert.IsType(handler); if (!tlsVerifyCert) { Assert.NotNull(socketsHandler.SslOptions.RemoteCertificateValidationCallback); @@ -117,9 +117,17 @@ public void tls_verify_cert(bool tlsVerifyCert) { } else { Assert.Null(socketsHandler.SslOptions.RemoteCertificateValidationCallback); } - } - +#else + var socketsHandler = Assert.IsType(handler); + if (!tlsVerifyCert) { + Assert.NotNull(socketsHandler.ServerCertificateValidationCallback); + Assert.True(socketsHandler.ServerCertificateValidationCallback!.Invoke(null!, default!, + default!, default)); + } else { + Assert.Null(socketsHandler.ServerCertificateValidationCallback); + } #endif + } [Fact] public void infinite_grpc_timeouts() { @@ -130,9 +138,15 @@ public void infinite_grpc_timeouts() { Assert.Equal(System.Threading.Timeout.InfiniteTimeSpan, result.ConnectivitySettings.KeepAliveTimeout); using var handler = result.CreateHttpMessageHandler?.Invoke(); +#if NET var socketsHandler = Assert.IsType(handler); Assert.Equal(System.Threading.Timeout.InfiniteTimeSpan, socketsHandler.KeepAlivePingTimeout); Assert.Equal(System.Threading.Timeout.InfiniteTimeSpan, socketsHandler.KeepAlivePingDelay); +#else + var winHttpHandler = Assert.IsType(handler); + Assert.Equal(System.Threading.Timeout.InfiniteTimeSpan, winHttpHandler.TcpKeepAliveTime); + Assert.Equal(System.Threading.Timeout.InfiniteTimeSpan, winHttpHandler.TcpKeepAliveInterval); +#endif } [Fact] @@ -327,16 +341,20 @@ private static string GetKeyValuePairs(EventStoreClientSettings settings, settings.DefaultDeadline.Value.TotalMilliseconds.ToString()); } -#if !GRPC_CORE if (settings.CreateHttpMessageHandler != null) { using var handler = settings.CreateHttpMessageHandler.Invoke(); +#if NET if (handler is SocketsHttpHandler socketsHttpHandler && socketsHttpHandler.SslOptions.RemoteCertificateValidationCallback != null) { pairs.Add("tlsVerifyCert", "false"); } - } +#else + if (handler is WinHttpHandler winHttpHandler && + winHttpHandler.ServerCertificateValidationCallback != null) { + pairs.Add("tlsVerifyCert", "false"); + } #endif - + } return string.Join("&", pairs.Select(pair => $"{getKey?.Invoke(pair.Key) ?? pair.Key}={pair.Value}")); } diff --git a/test/EventStore.Client.Tests/EventStore.Client.Tests.csproj b/test/EventStore.Client.Tests/EventStore.Client.Tests.csproj index e75038086..07a90a37a 100644 --- a/test/EventStore.Client.Tests/EventStore.Client.Tests.csproj +++ b/test/EventStore.Client.Tests/EventStore.Client.Tests.csproj @@ -5,24 +5,24 @@ + - - - - + - - all - runtime; build; native; contentfiles; analyzers - + + + + + + diff --git a/test/EventStore.Client.Tests/GrpcServerCapabilitiesClientTests.cs b/test/EventStore.Client.Tests/GrpcServerCapabilitiesClientTests.cs index e566b4034..ee7b49e83 100644 --- a/test/EventStore.Client.Tests/GrpcServerCapabilitiesClientTests.cs +++ b/test/EventStore.Client.Tests/GrpcServerCapabilitiesClientTests.cs @@ -1,3 +1,4 @@ +#if NET using System.Collections.Generic; using System.Net; using System.Threading.Tasks; @@ -88,3 +89,4 @@ public override Task GetSupportedMethods(Empty request, Server } } } +#endif From cfb07e97cd96e53b6cb00a2e100316249bcaa4ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Silveira?= Date: Tue, 17 Oct 2023 14:23:49 +0200 Subject: [PATCH 03/41] * trying to add dotnet 4.8 to ci --- .github/workflows/base.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/base.yml b/.github/workflows/base.yml index 48038fc6f..44d47c45f 100644 --- a/.github/workflows/base.yml +++ b/.github/workflows/base.yml @@ -13,10 +13,13 @@ jobs: strategy: fail-fast: false matrix: - framework: [net5.0, net6.0, net7.0] - os: [ubuntu-latest] + framework: [net4.8, net6.0, net7.0] + os: [ubuntu-latest, windows-latest] test: [Streams, PersistentSubscriptions, Operations, UserManagement, ProjectionManagement] configuration: [release] + exclude: + - framework: net4.8 + os: ubuntu-latest runs-on: ${{ matrix.os }} name: EventStore.Client.${{ matrix.test }}/${{ matrix.os }}/${{ matrix.framework }}/${{ inputs.docker-tag }} steps: @@ -33,7 +36,7 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: | - 5.0.x + 4.8.x 6.0.x 7.0.x - name: Compile From 6413ebe6e3de856291c685633fdf0c843121f7ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Silveira?= Date: Tue, 17 Oct 2023 14:35:22 +0200 Subject: [PATCH 04/41] * lets gooo --- .github/workflows/base.yml | 6 +++++- .github/workflows/publish.yml | 14 +++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/base.yml b/.github/workflows/base.yml index 44d47c45f..4280a8f88 100644 --- a/.github/workflows/base.yml +++ b/.github/workflows/base.yml @@ -20,6 +20,10 @@ jobs: exclude: - framework: net4.8 os: ubuntu-latest + - framework: net6.0 + os: windows-latest + - framework: net7.0 + os: windows-latest runs-on: ${{ matrix.os }} name: EventStore.Client.${{ matrix.test }}/${{ matrix.os }}/${{ matrix.framework }}/${{ inputs.docker-tag }} steps: @@ -36,7 +40,7 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: | - 4.8.x + 4.8.1 6.0.x 7.0.x - name: Compile diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 28dc70ccf..f520d4efd 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - framework: [ net5.0, net6.0, net7.0 ] + framework: [ net4.8, net6.0, net7.0 ] os: [ ubuntu-latest, windows-latest ] runs-on: ${{ matrix.os }} name: scan-vulnerabilities/${{ matrix.os }}/${{ matrix.framework }} @@ -25,7 +25,7 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: | - 5.0.x + 4.8.1 6.0.x 7.0.x - name: Scan for Vulnerabilities @@ -43,7 +43,7 @@ jobs: strategy: fail-fast: false matrix: - framework: [ net5.0, net6.0, net7.0 ] + framework: [ net4.8, net6.0, net7.0 ] services: esdb: image: ghcr.io/eventstore/eventstore:lts @@ -62,7 +62,7 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: | - 5.0.x + 4.8.1 6.0.x 7.0.x - name: Compile @@ -79,7 +79,7 @@ jobs: strategy: fail-fast: false matrix: - framework: [net5.0, net6.0, net7.0] + framework: [net4.8, net6.0, net7.0] os: [ubuntu-latest, windows-latest] configuration: [release] runs-on: ${{ matrix.os }} @@ -94,7 +94,7 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: | - 5.0.x + 4.8.1 6.0.x 7.0.x - name: Compile @@ -132,7 +132,7 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: | - 5.0.x + 4.8.1 6.0.x 7.0.x - name: Dotnet Pack From 46e3b0e0e5605008b4b050bebde8b97c257fd428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Silveira?= Date: Tue, 17 Oct 2023 14:40:17 +0200 Subject: [PATCH 05/41] is 4.8 already on latest windows? --- .github/workflows/base.yml | 1 - .github/workflows/publish.yml | 4 ---- 2 files changed, 5 deletions(-) diff --git a/.github/workflows/base.yml b/.github/workflows/base.yml index 4280a8f88..c0a3c95e8 100644 --- a/.github/workflows/base.yml +++ b/.github/workflows/base.yml @@ -40,7 +40,6 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: | - 4.8.1 6.0.x 7.0.x - name: Compile diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f520d4efd..5397abd33 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -25,7 +25,6 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: | - 4.8.1 6.0.x 7.0.x - name: Scan for Vulnerabilities @@ -62,7 +61,6 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: | - 4.8.1 6.0.x 7.0.x - name: Compile @@ -94,7 +92,6 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: | - 4.8.1 6.0.x 7.0.x - name: Compile @@ -132,7 +129,6 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: | - 4.8.1 6.0.x 7.0.x - name: Dotnet Pack From 830a438726b9140289373f730806aec7b08b3fd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Silveira?= Date: Tue, 17 Oct 2023 14:46:16 +0200 Subject: [PATCH 06/41] more fixes to test --- .github/workflows/publish.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5397abd33..7e6879223 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -16,6 +16,13 @@ jobs: matrix: framework: [ net4.8, net6.0, net7.0 ] os: [ ubuntu-latest, windows-latest ] + exclude: + - framework: net4.8 + os: ubuntu-latest + - framework: net6.0 + os: windows-latest + - framework: net7.0 + os: windows-latest runs-on: ${{ matrix.os }} name: scan-vulnerabilities/${{ matrix.os }}/${{ matrix.framework }} steps: @@ -80,6 +87,13 @@ jobs: framework: [net4.8, net6.0, net7.0] os: [ubuntu-latest, windows-latest] configuration: [release] + exclude: + - framework: net4.8 + os: ubuntu-latest + - framework: net6.0 + os: windows-latest + - framework: net7.0 + os: windows-latest runs-on: ${{ matrix.os }} name: test/EventStore.Client/${{ matrix.os }}/${{ matrix.framework }} steps: From bd50c682b64c6e6d79cf8b190aabc1115bdfbb45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Silveira?= Date: Tue, 17 Oct 2023 14:56:42 +0200 Subject: [PATCH 07/41] could it be net48 instead? --- .github/workflows/base.yml | 4 ++-- .github/workflows/publish.yml | 10 +++++----- samples/Directory.Build.props | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/base.yml b/.github/workflows/base.yml index c0a3c95e8..d32aca391 100644 --- a/.github/workflows/base.yml +++ b/.github/workflows/base.yml @@ -13,12 +13,12 @@ jobs: strategy: fail-fast: false matrix: - framework: [net4.8, net6.0, net7.0] + framework: [net48, net6.0, net7.0] os: [ubuntu-latest, windows-latest] test: [Streams, PersistentSubscriptions, Operations, UserManagement, ProjectionManagement] configuration: [release] exclude: - - framework: net4.8 + - framework: net48 os: ubuntu-latest - framework: net6.0 os: windows-latest diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7e6879223..f54dea187 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,10 +14,10 @@ jobs: strategy: fail-fast: false matrix: - framework: [ net4.8, net6.0, net7.0 ] + framework: [ net48, net6.0, net7.0 ] os: [ ubuntu-latest, windows-latest ] exclude: - - framework: net4.8 + - framework: net48 os: ubuntu-latest - framework: net6.0 os: windows-latest @@ -49,7 +49,7 @@ jobs: strategy: fail-fast: false matrix: - framework: [ net4.8, net6.0, net7.0 ] + framework: [ net48, net6.0, net7.0 ] services: esdb: image: ghcr.io/eventstore/eventstore:lts @@ -84,11 +84,11 @@ jobs: strategy: fail-fast: false matrix: - framework: [net4.8, net6.0, net7.0] + framework: [net48, net6.0, net7.0] os: [ubuntu-latest, windows-latest] configuration: [release] exclude: - - framework: net4.8 + - framework: net48 os: ubuntu-latest - framework: net6.0 os: windows-latest diff --git a/samples/Directory.Build.props b/samples/Directory.Build.props index feefe7675..4b318ded2 100644 --- a/samples/Directory.Build.props +++ b/samples/Directory.Build.props @@ -3,6 +3,6 @@ enable enable Exe - net5.0;net6.0;net7.0 + net6.0;net7.0 From 9a8b509247933db66c620f7422b67444691baef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Silveira?= Date: Tue, 17 Oct 2023 15:08:09 +0200 Subject: [PATCH 08/41] using windows-2019 runner added specific test step for windows net48 --- .github/workflows/base.yml | 12 ++++----- .github/workflows/publish.yml | 47 +++++++++++++++++++++++++---------- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/.github/workflows/base.yml b/.github/workflows/base.yml index d32aca391..69831a479 100644 --- a/.github/workflows/base.yml +++ b/.github/workflows/base.yml @@ -13,17 +13,17 @@ jobs: strategy: fail-fast: false matrix: - framework: [net48, net6.0, net7.0] - os: [ubuntu-latest, windows-latest] - test: [Streams, PersistentSubscriptions, Operations, UserManagement, ProjectionManagement] - configuration: [release] + framework: [ net48, net6.0, net7.0 ] + os: [ ubuntu-latest, windows-2019 ] + test: [ Streams, PersistentSubscriptions, Operations, UserManagement, ProjectionManagement ] + configuration: [ release ] exclude: - framework: net48 os: ubuntu-latest - framework: net6.0 - os: windows-latest + os: windows-2019 - framework: net7.0 - os: windows-latest + os: windows-2019 runs-on: ${{ matrix.os }} name: EventStore.Client.${{ matrix.test }}/${{ matrix.os }}/${{ matrix.framework }}/${{ inputs.docker-tag }} steps: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f54dea187..b0e8abb62 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,14 +15,14 @@ jobs: fail-fast: false matrix: framework: [ net48, net6.0, net7.0 ] - os: [ ubuntu-latest, windows-latest ] + os: [ ubuntu-latest, windows-2019 ] exclude: - framework: net48 os: ubuntu-latest - framework: net6.0 - os: windows-latest + os: windows-2019 - framework: net7.0 - os: windows-latest + os: windows-2019 runs-on: ${{ matrix.os }} name: scan-vulnerabilities/${{ matrix.os }}/${{ matrix.framework }} steps: @@ -84,16 +84,9 @@ jobs: strategy: fail-fast: false matrix: - framework: [net48, net6.0, net7.0] - os: [ubuntu-latest, windows-latest] - configuration: [release] - exclude: - - framework: net48 - os: ubuntu-latest - - framework: net6.0 - os: windows-latest - - framework: net7.0 - os: windows-latest + framework: [ net6.0, net7.0 ] + os: [ ubuntu-latest ] + configuration: [ release ] runs-on: ${{ matrix.os }} name: test/EventStore.Client/${{ matrix.os }}/${{ matrix.framework }} steps: @@ -119,6 +112,34 @@ jobs: --logger:"GitHubActions;report-warnings=false" --logger:"console;verbosity=normal" \ --framework ${{ matrix.framework }} \ test/EventStore.Client.Tests + + test-windows: + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + framework: [ net48 ] + os: [ windows-2019 ] + configuration: [ release ] + runs-on: ${{ matrix.os }} + name: test/EventStore.Client/${{ matrix.os }}/${{ matrix.framework }} + steps: + - name: Checkout + uses: actions/checkout@v3 + - shell: bash + run: | + git fetch --prune --unshallow + - name: Compile + shell: bash + run: | + dotnet build --configuration ${{ matrix.configuration }} --framework ${{ matrix.framework }} src/EventStore.Client + - name: Run Tests + shell: bash + run: | + dotnet test --configuration ${{ matrix.configuration }} --blame \ + --logger:"GitHubActions;report-warnings=false" --logger:"console;verbosity=normal" \ + --framework ${{ matrix.framework }} \ + test/EventStore.Client.Tests publish: timeout-minutes: 5 From 0cdfa9044a4d9bb3bafb8b40326107008d7225e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Silveira?= Date: Tue, 17 Oct 2023 15:14:00 +0200 Subject: [PATCH 09/41] interesting... --- .github/workflows/publish.yml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b0e8abb62..0f4fb9702 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -44,12 +44,20 @@ jobs: build-samples: timeout-minutes: 5 - name: build-samples/${{ matrix.framework }} - runs-on: ubuntu-latest + name: build-samples/${{ matrix.os }}/${{ matrix.framework }} strategy: fail-fast: false matrix: framework: [ net48, net6.0, net7.0 ] + os: [ ubuntu-latest, windows-2019 ] + exclude: + - framework: net48 + os: ubuntu-latest + - framework: net6.0 + os: windows-2019 + - framework: net7.0 + os: windows-2019 + runs-on: ${{ matrix.os }} services: esdb: image: ghcr.io/eventstore/eventstore:lts @@ -129,6 +137,12 @@ jobs: - shell: bash run: | git fetch --prune --unshallow + - name: Install dotnet SDKs + uses: actions/setup-dotnet@v3 + with: + dotnet-version: | + 6.0.x + 7.0.x - name: Compile shell: bash run: | From 23144a00fc2e37de1ff27fa8b6b820277d2fbf82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Silveira?= Date: Tue, 17 Oct 2023 15:22:30 +0200 Subject: [PATCH 10/41] single test step now --- .github/workflows/publish.yml | 58 ++++++++--------------------------- 1 file changed, 12 insertions(+), 46 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 0f4fb9702..969c31622 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -48,15 +48,8 @@ jobs: strategy: fail-fast: false matrix: - framework: [ net48, net6.0, net7.0 ] - os: [ ubuntu-latest, windows-2019 ] - exclude: - - framework: net48 - os: ubuntu-latest - - framework: net6.0 - os: windows-2019 - - framework: net7.0 - os: windows-2019 + framework: [ net6.0, net7.0 ] + os: [ ubuntu-latest ] runs-on: ${{ matrix.os }} services: esdb: @@ -92,9 +85,16 @@ jobs: strategy: fail-fast: false matrix: - framework: [ net6.0, net7.0 ] - os: [ ubuntu-latest ] + framework: [ net48, net6.0, net7.0 ] + os: [ ubuntu-latest, windows-2019 ] configuration: [ release ] + exclude: + - framework: net48 + os: ubuntu-latest + - framework: net6.0 + os: windows-2019 + - framework: net7.0 + os: windows-2019 runs-on: ${{ matrix.os }} name: test/EventStore.Client/${{ matrix.os }}/${{ matrix.framework }} steps: @@ -120,41 +120,7 @@ jobs: --logger:"GitHubActions;report-warnings=false" --logger:"console;verbosity=normal" \ --framework ${{ matrix.framework }} \ test/EventStore.Client.Tests - - test-windows: - timeout-minutes: 10 - strategy: - fail-fast: false - matrix: - framework: [ net48 ] - os: [ windows-2019 ] - configuration: [ release ] - runs-on: ${{ matrix.os }} - name: test/EventStore.Client/${{ matrix.os }}/${{ matrix.framework }} - steps: - - name: Checkout - uses: actions/checkout@v3 - - shell: bash - run: | - git fetch --prune --unshallow - - name: Install dotnet SDKs - uses: actions/setup-dotnet@v3 - with: - dotnet-version: | - 6.0.x - 7.0.x - - name: Compile - shell: bash - run: | - dotnet build --configuration ${{ matrix.configuration }} --framework ${{ matrix.framework }} src/EventStore.Client - - name: Run Tests - shell: bash - run: | - dotnet test --configuration ${{ matrix.configuration }} --blame \ - --logger:"GitHubActions;report-warnings=false" --logger:"console;verbosity=normal" \ - --framework ${{ matrix.framework }} \ - test/EventStore.Client.Tests - + publish: timeout-minutes: 5 needs: [ vulnerability-scan, test, build-samples ] From 45afde5e6337b1f5565e9b40a5f732a4fd5929d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Silveira?= Date: Tue, 17 Oct 2023 16:08:00 +0200 Subject: [PATCH 11/41] trying to figure out some stuff here with multiple windows runners --- .github/workflows/base.yml | 8 +++++++- .github/workflows/publish.yml | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/base.yml b/.github/workflows/base.yml index 69831a479..d414b693d 100644 --- a/.github/workflows/base.yml +++ b/.github/workflows/base.yml @@ -14,16 +14,22 @@ jobs: fail-fast: false matrix: framework: [ net48, net6.0, net7.0 ] - os: [ ubuntu-latest, windows-2019 ] + os: [ ubuntu-latest, windows-2019, windows-latest ] test: [ Streams, PersistentSubscriptions, Operations, UserManagement, ProjectionManagement ] configuration: [ release ] exclude: + - framework: net48 + os: ubuntu-2019 - framework: net48 os: ubuntu-latest - framework: net6.0 os: windows-2019 - framework: net7.0 os: windows-2019 + - framework: net6.0 + os: windows-latest + - framework: net7.0 + os: windows-latest runs-on: ${{ matrix.os }} name: EventStore.Client.${{ matrix.test }}/${{ matrix.os }}/${{ matrix.framework }}/${{ inputs.docker-tag }} steps: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 969c31622..1dab4e0aa 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -86,15 +86,21 @@ jobs: fail-fast: false matrix: framework: [ net48, net6.0, net7.0 ] - os: [ ubuntu-latest, windows-2019 ] + os: [ ubuntu-latest, windows-2019, windows-latest ] configuration: [ release ] exclude: + - framework: net48 + os: ubuntu-2019 - framework: net48 os: ubuntu-latest - framework: net6.0 os: windows-2019 - framework: net7.0 os: windows-2019 + - framework: net6.0 + os: windows-latest + - framework: net7.0 + os: windows-latest runs-on: ${{ matrix.os }} name: test/EventStore.Client/${{ matrix.os }}/${{ matrix.framework }} steps: From 10d666041bb1430ca375edab67612a34cb060c84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Silveira?= Date: Tue, 17 Oct 2023 16:14:11 +0200 Subject: [PATCH 12/41] changed the order of the docker pull step fixed gencert (on macos at least) --- .github/workflows/base.yml | 8 ++++---- gencert.sh | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/base.yml b/.github/workflows/base.yml index d414b693d..c77290132 100644 --- a/.github/workflows/base.yml +++ b/.github/workflows/base.yml @@ -38,10 +38,6 @@ jobs: - shell: bash run: | git fetch --prune --unshallow - - name: Pull EventStore Image - shell: bash - run: | - docker pull ghcr.io/eventstore/eventstore:${{ inputs.docker-tag }} - name: Install dotnet SDKs uses: actions/setup-dotnet@v3 with: @@ -52,6 +48,10 @@ jobs: shell: bash run: | dotnet build --configuration ${{ matrix.configuration }} --framework ${{ matrix.framework }} src/EventStore.Client.${{ matrix.test }} + - name: Pull EventStore Image + shell: bash + run: | + docker pull ghcr.io/eventstore/eventstore:${{ inputs.docker-tag }} - name: Run Tests shell: bash env: diff --git a/gencert.sh b/gencert.sh index 31ff8bb40..aa05e4342 100755 --- a/gencert.sh +++ b/gencert.sh @@ -10,4 +10,4 @@ docker run --rm --volume $PWD/certs:/tmp --user $(id -u):$(id -g) eventstore/es- docker run --rm --volume $PWD/certs:/tmp --user $(id -u):$(id -g) eventstore/es-gencert-cli:1.0.1 create-node -ca-certificate /tmp/ca/ca.crt -ca-key /tmp/ca/ca.key -out /tmp/node -ip-addresses 127.0.0.1 -dns-names localhost -chmod 0755 -R ./certs +chmod -R 0755 ./certs From fa41ba11a063170d0020ebabc69e3974caed293a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Silveira?= Date: Tue, 17 Oct 2023 16:34:55 +0200 Subject: [PATCH 13/41] minor attempt to simplify matrices --- .github/workflows/base.yml | 12 ++---------- .github/workflows/publish.yml | 26 ++++++++------------------ 2 files changed, 10 insertions(+), 28 deletions(-) diff --git a/.github/workflows/base.yml b/.github/workflows/base.yml index c77290132..144834f79 100644 --- a/.github/workflows/base.yml +++ b/.github/workflows/base.yml @@ -14,21 +14,13 @@ jobs: fail-fast: false matrix: framework: [ net48, net6.0, net7.0 ] - os: [ ubuntu-latest, windows-2019, windows-latest ] + os: [ ubuntu-latest, windows-latest ] test: [ Streams, PersistentSubscriptions, Operations, UserManagement, ProjectionManagement ] configuration: [ release ] exclude: - - framework: net48 - os: ubuntu-2019 - framework: net48 os: ubuntu-latest - - framework: net6.0 - os: windows-2019 - - framework: net7.0 - os: windows-2019 - - framework: net6.0 - os: windows-latest - - framework: net7.0 + - framework: [ net6.0, net7.0 ] os: windows-latest runs-on: ${{ matrix.os }} name: EventStore.Client.${{ matrix.test }}/${{ matrix.os }}/${{ matrix.framework }}/${{ inputs.docker-tag }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1dab4e0aa..5bdac612f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,14 +15,12 @@ jobs: fail-fast: false matrix: framework: [ net48, net6.0, net7.0 ] - os: [ ubuntu-latest, windows-2019 ] + os: [ ubuntu-latest, windows-latest ] exclude: - framework: net48 os: ubuntu-latest - - framework: net6.0 - os: windows-2019 - - framework: net7.0 - os: windows-2019 + - framework: [ net6.0, net7.0 ] + os: windows-latest runs-on: ${{ matrix.os }} name: scan-vulnerabilities/${{ matrix.os }}/${{ matrix.framework }} steps: @@ -86,21 +84,13 @@ jobs: fail-fast: false matrix: framework: [ net48, net6.0, net7.0 ] - os: [ ubuntu-latest, windows-2019, windows-latest ] + os: [ ubuntu-latest, windows-latest ] configuration: [ release ] exclude: - - framework: net48 - os: ubuntu-2019 - - framework: net48 - os: ubuntu-latest - - framework: net6.0 - os: windows-2019 - - framework: net7.0 - os: windows-2019 - - framework: net6.0 - os: windows-latest - - framework: net7.0 - os: windows-latest + - framework: net48 + os: ubuntu-latest + - framework: [ net6.0, net7.0 ] + os: windows-latest runs-on: ${{ matrix.os }} name: test/EventStore.Client/${{ matrix.os }}/${{ matrix.framework }} steps: From 8adc0991951df095a39594df811927da16458640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Silveira?= Date: Tue, 17 Oct 2023 16:40:21 +0200 Subject: [PATCH 14/41] nop --- .github/workflows/base.yml | 4 +++- .github/workflows/publish.yml | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/base.yml b/.github/workflows/base.yml index 144834f79..66d379fa8 100644 --- a/.github/workflows/base.yml +++ b/.github/workflows/base.yml @@ -20,7 +20,9 @@ jobs: exclude: - framework: net48 os: ubuntu-latest - - framework: [ net6.0, net7.0 ] + - framework: net6.0 + os: windows-latest + - framework: net7.0 os: windows-latest runs-on: ${{ matrix.os }} name: EventStore.Client.${{ matrix.test }}/${{ matrix.os }}/${{ matrix.framework }}/${{ inputs.docker-tag }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5bdac612f..674a199d7 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -19,7 +19,9 @@ jobs: exclude: - framework: net48 os: ubuntu-latest - - framework: [ net6.0, net7.0 ] + - framework: net6.0 + os: windows-latest + - framework: net7.0 os: windows-latest runs-on: ${{ matrix.os }} name: scan-vulnerabilities/${{ matrix.os }}/${{ matrix.framework }} @@ -89,7 +91,9 @@ jobs: exclude: - framework: net48 os: ubuntu-latest - - framework: [ net6.0, net7.0 ] + - framework: net6.0 + os: windows-latest + - framework: net7.0 os: windows-latest runs-on: ${{ matrix.os }} name: test/EventStore.Client/${{ matrix.os }}/${{ matrix.framework }} From 4b9e3e142c28218a9137e907399480ab8cd9501a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Silveira?= Date: Tue, 17 Oct 2023 17:29:17 +0200 Subject: [PATCH 15/41] it seems net48 can run on ubuntu with mono --- .github/workflows/base.yml | 9 +-------- .github/workflows/publish.yml | 19 ++----------------- 2 files changed, 3 insertions(+), 25 deletions(-) diff --git a/.github/workflows/base.yml b/.github/workflows/base.yml index 66d379fa8..db0bc33e5 100644 --- a/.github/workflows/base.yml +++ b/.github/workflows/base.yml @@ -14,16 +14,9 @@ jobs: fail-fast: false matrix: framework: [ net48, net6.0, net7.0 ] - os: [ ubuntu-latest, windows-latest ] + os: [ ubuntu-latest ] test: [ Streams, PersistentSubscriptions, Operations, UserManagement, ProjectionManagement ] configuration: [ release ] - exclude: - - framework: net48 - os: ubuntu-latest - - framework: net6.0 - os: windows-latest - - framework: net7.0 - os: windows-latest runs-on: ${{ matrix.os }} name: EventStore.Client.${{ matrix.test }}/${{ matrix.os }}/${{ matrix.framework }}/${{ inputs.docker-tag }} steps: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 674a199d7..cd4a50812 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,14 +15,7 @@ jobs: fail-fast: false matrix: framework: [ net48, net6.0, net7.0 ] - os: [ ubuntu-latest, windows-latest ] - exclude: - - framework: net48 - os: ubuntu-latest - - framework: net6.0 - os: windows-latest - - framework: net7.0 - os: windows-latest + os: [ ubuntu-latest ] runs-on: ${{ matrix.os }} name: scan-vulnerabilities/${{ matrix.os }}/${{ matrix.framework }} steps: @@ -48,7 +41,7 @@ jobs: strategy: fail-fast: false matrix: - framework: [ net6.0, net7.0 ] + framework: [ net48, net6.0, net7.0 ] os: [ ubuntu-latest ] runs-on: ${{ matrix.os }} services: @@ -88,13 +81,6 @@ jobs: framework: [ net48, net6.0, net7.0 ] os: [ ubuntu-latest, windows-latest ] configuration: [ release ] - exclude: - - framework: net48 - os: ubuntu-latest - - framework: net6.0 - os: windows-latest - - framework: net7.0 - os: windows-latest runs-on: ${{ matrix.os }} name: test/EventStore.Client/${{ matrix.os }}/${{ matrix.framework }} steps: @@ -173,4 +159,3 @@ jobs: dotnet nuget list source dotnet tool restore find . -name "*.nupkg" | xargs -n1 dotnet nuget push --api-key=${{ secrets.nuget_key }} --source https://api.nuget.org/v3/index.json --skip-duplicate - From a87385ff3c34c06dccebd441a5cb3d08fe90014f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Silveira?= Date: Tue, 17 Oct 2023 17:49:16 +0200 Subject: [PATCH 16/41] install mono ai ai ai --- .github/workflows/base.yml | 4 ++++ .github/workflows/publish.yml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/base.yml b/.github/workflows/base.yml index db0bc33e5..dae385f28 100644 --- a/.github/workflows/base.yml +++ b/.github/workflows/base.yml @@ -31,6 +31,10 @@ jobs: dotnet-version: | 6.0.x 7.0.x + - name: Install Mono + shell: bash + run: | + apt-get install -y mono-complete - name: Compile shell: bash run: | diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index cd4a50812..1d3e974c3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -95,6 +95,10 @@ jobs: dotnet-version: | 6.0.x 7.0.x + - name: Install Mono + shell: bash + run: | + apt-get install -y mono-complete - name: Compile shell: bash run: | From bb1dfed7d088b4bc91b3d5777c4f42984162dfef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Silveira?= Date: Tue, 17 Oct 2023 17:54:08 +0200 Subject: [PATCH 17/41] added the same to the build samples step --- .github/workflows/publish.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1d3e974c3..15307bea0 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -64,6 +64,10 @@ jobs: dotnet-version: | 6.0.x 7.0.x + - name: Install Mono + shell: bash + run: | + apt-get install -y mono-complete - name: Compile shell: bash run: | From 071a832fee0e2905c9b675151c52a30ba312e31f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Silveira?= Date: Tue, 17 Oct 2023 18:05:31 +0200 Subject: [PATCH 18/41] sudo? --- .github/workflows/base.yml | 3 ++- .github/workflows/publish.yml | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/base.yml b/.github/workflows/base.yml index dae385f28..2c0342e03 100644 --- a/.github/workflows/base.yml +++ b/.github/workflows/base.yml @@ -34,7 +34,8 @@ jobs: - name: Install Mono shell: bash run: | - apt-get install -y mono-complete + sudo apt-get update + sudo apt-get install -y mono-complete - name: Compile shell: bash run: | diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 15307bea0..dda0b099c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -67,7 +67,8 @@ jobs: - name: Install Mono shell: bash run: | - apt-get install -y mono-complete + sudo apt-get update + sudo apt-get install -y mono-complete - name: Compile shell: bash run: | @@ -102,7 +103,8 @@ jobs: - name: Install Mono shell: bash run: | - apt-get install -y mono-complete + sudo apt-get update + sudo apt-get install -y mono-complete - name: Compile shell: bash run: | From e8ce300554d9b293a3b4a05353b62a6dd6e6b17f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Silveira?= Date: Tue, 17 Oct 2023 18:16:58 +0200 Subject: [PATCH 19/41] already has latest mongo --- .github/workflows/base.yml | 7 +------ .github/workflows/publish.yml | 10 ---------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/.github/workflows/base.yml b/.github/workflows/base.yml index 2c0342e03..1cf5a9d7a 100644 --- a/.github/workflows/base.yml +++ b/.github/workflows/base.yml @@ -30,12 +30,7 @@ jobs: with: dotnet-version: | 6.0.x - 7.0.x - - name: Install Mono - shell: bash - run: | - sudo apt-get update - sudo apt-get install -y mono-complete + 7.0.x - name: Compile shell: bash run: | diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index dda0b099c..cd4a50812 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -64,11 +64,6 @@ jobs: dotnet-version: | 6.0.x 7.0.x - - name: Install Mono - shell: bash - run: | - sudo apt-get update - sudo apt-get install -y mono-complete - name: Compile shell: bash run: | @@ -100,11 +95,6 @@ jobs: dotnet-version: | 6.0.x 7.0.x - - name: Install Mono - shell: bash - run: | - sudo apt-get update - sudo apt-get install -y mono-complete - name: Compile shell: bash run: | From 26869d93c7842a26e39e9ad0998080b83c945a17 Mon Sep 17 00:00:00 2001 From: thefringeninja <495495+thefringeninja@users.noreply.github.com> Date: Tue, 17 Oct 2023 09:51:10 -0700 Subject: [PATCH 20/41] reference .net reference assemblies to enable mono compile on linux --- Directory.Build.props | 2 ++ src/Directory.Build.props | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 37918c2f4..e03134ec3 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -18,5 +18,7 @@ + + diff --git a/src/Directory.Build.props b/src/Directory.Build.props index ffe2a4475..452f71834 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -33,7 +33,7 @@ - + From 670c60e68df632284f41c1ead976c0b84988969a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Silveira?= Date: Mon, 6 Nov 2023 10:30:05 +0100 Subject: [PATCH 21/41] upgraded serilog packages --- test/Directory.Build.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 8a6152d89..3500247cf 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -10,8 +10,8 @@ - - + + From 9fb6be3e60762789f50d708d33e6937918a64260 Mon Sep 17 00:00:00 2001 From: William Chong Date: Fri, 12 Jan 2024 18:28:20 +0400 Subject: [PATCH 22/41] Remove errors --- .github/workflows/publish.yml | 27 +-- Directory.Build.props | 48 ++-- samples/Directory.Build.props | 27 +-- src/Directory.Build.props | 119 +++++----- .../EpochExtensions.cs | 12 +- ...ntStoreProjectionManagementClient.State.cs | 8 + .../EventStore.Client.Streams.csproj | 1 + .../EventStoreClient.Append.cs | 4 +- src/EventStore.Client/ChannelFactory.cs | 2 +- src/EventStore.Client/HashCode.cs | 11 +- .../Interceptors/TypedExceptionInterceptor.cs | 7 +- test/Directory.Build.props | 91 ++++---- .../EventStore.Client.Operations.Tests.csproj | 5 - ...lient.PersistentSubscriptions.Tests.csproj | 25 +- .../SubscriptionToAll/get_info.cs | 15 +- .../SubscriptionToStream/get_info.cs | 14 +- ...e.Client.ProjectionManagement.Tests.csproj | 5 - .../EventStore.Client.Streams.Tests.csproj | 9 +- .../Read/EventBinaryData.cs | 6 +- .../ApplicationInfo.cs | 12 +- .../EventStore.Client.Tests.Common.csproj | 6 - .../EventStoreClientFixtureBase.cs | 147 ------------ .../EventStoreTestServer.cs | 139 ------------ .../EventStoreTestServerCluster.cs | 94 -------- .../Fixtures/Base/EventStoreTestServer.cs | 214 +++++++++--------- .../Base/EventStoreTestServerCluster.cs | 13 +- .../Base/EventStoreTestServerExternal.cs | 4 +- .../Fixtures/EventStoreTestNode.cs | 11 +- .../FluentDockerServiceExtensions.cs | 46 +++- .../GlobalEnvironment.cs | 2 +- .../ConnectionStringTests.cs | 21 +- .../EventStore.Client.Tests.csproj | 25 +- ...ntStore.Client.UserManagement.Tests.csproj | 5 - 33 files changed, 402 insertions(+), 773 deletions(-) delete mode 100644 test/EventStore.Client.Tests.Common/EventStoreClientFixtureBase.cs delete mode 100644 test/EventStore.Client.Tests.Common/EventStoreTestServer.cs delete mode 100644 test/EventStore.Client.Tests.Common/EventStoreTestServerCluster.cs diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5b0de0dfb..67d3166dc 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,7 +15,7 @@ jobs: fail-fast: false matrix: framework: [ net48, net6.0, net7.0, net8.0 ] - os: [ ubuntu-latest, windows-latest ] + os: [ ubuntu-latest ] runs-on: ${{ matrix.os }} name: scan-vulnerabilities/${{ matrix.os }}/${{ matrix.framework }} steps: @@ -27,7 +27,6 @@ jobs: dotnet-version: | 6.0.x 7.0.x - 8.0.x - name: Scan for Vulnerabilities shell: bash run: | @@ -42,19 +41,15 @@ jobs: strategy: fail-fast: false matrix: -<<<<<<< HEAD - framework: [ net48, net6.0, net7.0 ] + framework: [ net48, net6.0, net7.0, net8.0 ] os: [ ubuntu-latest ] runs-on: ${{ matrix.os }} -======= - framework: [ net8.0 ] ->>>>>>> master services: esdb: image: ghcr.io/eventstore/eventstore:lts env: EVENTSTORE_INSECURE: true - EVENTSTORE_MEM_DB: false + EVENTSTORE_MEMDB: true EVENTSTORE_RUN_PROJECTIONS: all EVENTSTORE_START_STANDARD_PROJECTIONS: true ports: @@ -67,12 +62,8 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: | -<<<<<<< HEAD 6.0.x 7.0.x -======= - 8.0.x ->>>>>>> master - name: Compile shell: bash run: | @@ -83,15 +74,11 @@ jobs: find samples/ -type f -iname "*.csproj" -print0 | xargs -0L1 dotnet run --framework ${{ matrix.framework }} --project test: - timeout-minutes: 20 + timeout-minutes: 10 strategy: fail-fast: false matrix: -<<<<<<< HEAD framework: [ net48, net6.0, net7.0 ] -======= - framework: [ net6.0, net7.0, net8.0 ] ->>>>>>> master os: [ ubuntu-latest, windows-latest ] configuration: [ release ] runs-on: ${{ matrix.os }} @@ -108,7 +95,6 @@ jobs: dotnet-version: | 6.0.x 7.0.x - 8.0.x - name: Compile shell: bash run: | @@ -146,7 +132,6 @@ jobs: dotnet-version: | 6.0.x 7.0.x - 8.0.x - name: Dotnet Pack shell: bash run: | @@ -173,8 +158,4 @@ jobs: run: | dotnet nuget list source dotnet tool restore -<<<<<<< HEAD - find . -name "*.nupkg" | xargs -n1 dotnet nuget push --api-key=${{ secrets.nuget_key }} --source https://api.nuget.org/v3/index.json --skip-duplicate -======= find . -name "*.nupkg" | xargs -n1 dotnet nuget push --api-key=${{ secrets.nuget_key }} --source https://api.nuget.org/v3/index.json --skip-duplicate ->>>>>>> master diff --git a/Directory.Build.props b/Directory.Build.props index 511bd66eb..c534bff7e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,30 +1,26 @@ - - net6.0;net7.0;net48;net8.0 - true - enable - enable - true - - preview + + net48;net6.0;net7.0;net8.0 + true + enable + enable + true + true + preview - - true - true + Debug + full + pdbonly - Debug - full - pdbonly - $(MSBuildThisFileDirectory)\bin\$(Configuration)\$(MSBuildProjectName)\ - EventStore.Client - true - 2.59.0 - 2.59.0 - - - - - - - + true + 2.59.0 + 2.59.0 + + + + + + + + diff --git a/samples/Directory.Build.props b/samples/Directory.Build.props index 4fee7ef7c..2be8b7798 100644 --- a/samples/Directory.Build.props +++ b/samples/Directory.Build.props @@ -1,18 +1,15 @@ - - enable - enable - Exe + + net48;net8.0 + enable + enable + true + Exe + preview + - - true - preview - - net6.0;net7.0;net8.0 - - - - - - + + + + diff --git a/src/Directory.Build.props b/src/Directory.Build.props index e307a9971..c40effecf 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,65 +1,58 @@ - - - - EventStore.Client - - - - $(MSBuildProjectName.Remove(0,18)) - $(ESPackageIdSuffix.ToLower()).proto - ../EventStore.Client.Common/protos/$(ESProto) - EventStore.Client.Grpc.$(ESPackageIdSuffix) - - - - - - - - - ouro.png - LICENSE.md - https://eventstore.com - false - https://eventstore.com/blog/ - eventstore client grpc - Event Store Ltd - Copyright 2012-2023 Event Store Ltd - v - true - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers - - - - - - - - - - - <_Parameter1>$(ProjectName).Tests - - - <_Parameter1>$(ProjectName).Tests.Common - - - <_Parameter1>EventStore.Client - - + + + EventStore.Client + + + + $(MSBuildProjectName.Remove(0,18)) + $(ESPackageIdSuffix.ToLower()).proto + ../EventStore.Client.Common/protos/$(ESProto) + EventStore.Client.Grpc.$(ESPackageIdSuffix) + + + + + + + + + ouro.png + LICENSE.md + https://eventstore.com + false + https://eventstore.com/blog/ + eventstore client grpc + Event Store Ltd + Copyright 2012-2020 Event Store Ltd + v + true + + + + + + + + + + + + + + + + + + + + <_Parameter1>$(ProjectName).Tests + + + <_Parameter1>$(ProjectName).Tests.Common + + + <_Parameter1>EventStore.Client + + diff --git a/src/EventStore.Client.Common/EpochExtensions.cs b/src/EventStore.Client.Common/EpochExtensions.cs index e5dd9c838..e4d84dede 100644 --- a/src/EventStore.Client.Common/EpochExtensions.cs +++ b/src/EventStore.Client.Common/EpochExtensions.cs @@ -1,5 +1,7 @@ +using System; + namespace EventStore.Client { - internal static class EpochExtensions { + internal static class EpochExtensions { private const long TicksPerMillisecond = 10000; private const long TicksPerSecond = TicksPerMillisecond * 1000; private const long TicksPerMinute = TicksPerSecond * 60; @@ -14,8 +16,10 @@ internal static class EpochExtensions { private static readonly DateTime UnixEpoch = new(UnixEpochTicks, DateTimeKind.Utc); - public static DateTime FromTicksSinceEpoch(this long value) => new(UnixEpoch.Ticks + value, DateTimeKind.Utc); + public static DateTime FromTicksSinceEpoch(this long value) => + new DateTime(UnixEpoch.Ticks + value, DateTimeKind.Utc); - public static long ToTicksSinceEpoch(this DateTime value) => (value - UnixEpoch).Ticks; - }; + public static long ToTicksSinceEpoch(this DateTime value) => + (value - UnixEpoch).Ticks; + } } diff --git a/src/EventStore.Client.ProjectionManagement/EventStoreProjectionManagementClient.State.cs b/src/EventStore.Client.ProjectionManagement/EventStoreProjectionManagementClient.State.cs index f1a4294fc..73895e52e 100644 --- a/src/EventStore.Client.ProjectionManagement/EventStoreProjectionManagementClient.State.cs +++ b/src/EventStore.Client.ProjectionManagement/EventStoreProjectionManagementClient.State.cs @@ -25,11 +25,13 @@ public async Task GetResultAsync(string name, string? partition = CancellationToken cancellationToken = default) { var value = await GetResultInternalAsync(name, partition, deadline, userCredentials, cancellationToken) .ConfigureAwait(false); + #if NET await using var stream = new MemoryStream(); #else using var stream = new MemoryStream(); #endif + await using var writer = new Utf8JsonWriter(stream); var serializer = new ValueSerializer(); serializer.Write(writer, value, DefaultJsonSerializerOptions); @@ -56,11 +58,13 @@ public async Task GetResultAsync(string name, string? partition = null, CancellationToken cancellationToken = default) { var value = await GetResultInternalAsync(name, partition, deadline, userCredentials, cancellationToken) .ConfigureAwait(false); + #if NET await using var stream = new MemoryStream(); #else using var stream = new MemoryStream(); #endif + await using var writer = new Utf8JsonWriter(stream); var serializer = new ValueSerializer(); serializer.Write(writer, value, DefaultJsonSerializerOptions); @@ -99,11 +103,13 @@ public async Task GetStateAsync(string name, string? partition = n CancellationToken cancellationToken = default) { var value = await GetStateInternalAsync(name, partition, deadline, userCredentials, cancellationToken) .ConfigureAwait(false); + #if NET await using var stream = new MemoryStream(); #else using var stream = new MemoryStream(); #endif + await using var writer = new Utf8JsonWriter(stream); var serializer = new ValueSerializer(); serializer.Write(writer, value, DefaultJsonSerializerOptions); @@ -129,11 +135,13 @@ public async Task GetStateAsync(string name, string? partition = null, UserCredentials? userCredentials = null, CancellationToken cancellationToken = default) { var value = await GetStateInternalAsync(name, partition, deadline, userCredentials, cancellationToken) .ConfigureAwait(false); + #if NET await using var stream = new MemoryStream(); #else using var stream = new MemoryStream(); #endif + await using var writer = new Utf8JsonWriter(stream); var serializer = new ValueSerializer(); serializer.Write(writer, value, DefaultJsonSerializerOptions); diff --git a/src/EventStore.Client.Streams/EventStore.Client.Streams.csproj b/src/EventStore.Client.Streams/EventStore.Client.Streams.csproj index 1dc68f255..6d85d65a9 100644 --- a/src/EventStore.Client.Streams/EventStore.Client.Streams.csproj +++ b/src/EventStore.Client.Streams/EventStore.Client.Streams.csproj @@ -3,6 +3,7 @@ The GRPC client API for Event Store Streams. Get the open source or commercial versions of Event Store server from https://eventstore.com/ + diff --git a/src/EventStore.Client.Streams/EventStoreClient.Append.cs b/src/EventStore.Client.Streams/EventStoreClient.Append.cs index a0e73c6e5..6a9149f04 100644 --- a/src/EventStore.Client.Streams/EventStoreClient.Append.cs +++ b/src/EventStore.Client.Streams/EventStoreClient.Append.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using System.Threading.Channels; +using System.Runtime.CompilerServices; using Google.Protobuf; using EventStore.Client.Streams; using Grpc.Core; @@ -340,7 +340,7 @@ public void Dispose() { _channel.Writer.TryComplete(); } } - + private static async IAsyncEnumerable ReadAllAsync(ChannelReader reader, [EnumeratorCancellation] CancellationToken cancellationToken = default) { while (await reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) diff --git a/src/EventStore.Client/ChannelFactory.cs b/src/EventStore.Client/ChannelFactory.cs index f920fde39..594394469 100644 --- a/src/EventStore.Client/ChannelFactory.cs +++ b/src/EventStore.Client/ChannelFactory.cs @@ -48,7 +48,7 @@ HttpMessageHandler CreateHandler() { EnableMultipleHttp2Connections = true }; #endif - } + } } } } diff --git a/src/EventStore.Client/HashCode.cs b/src/EventStore.Client/HashCode.cs index 31f9c2514..3bc86fc6d 100644 --- a/src/EventStore.Client/HashCode.cs +++ b/src/EventStore.Client/HashCode.cs @@ -32,6 +32,15 @@ public HashCode Combine(IEnumerable? values) where T: struct => public HashCode Combine(IEnumerable? values) => (values ?? Enumerable.Empty()).Aggregate(Hash, (previous, value) => previous.Combine(value)); + public static int Combine(Uuid values, byte[] data, byte[] metadata) { + unchecked { + var hash = HashCode.Hash.Combine(values); + hash = hash.Combine(data); + hash = hash.Combine(metadata); + return hash; + } + } + public static implicit operator int(HashCode value) => value._value; - } + } } diff --git a/src/EventStore.Client/Interceptors/TypedExceptionInterceptor.cs b/src/EventStore.Client/Interceptors/TypedExceptionInterceptor.cs index badcc4577..75754f95f 100644 --- a/src/EventStore.Client/Interceptors/TypedExceptionInterceptor.cs +++ b/src/EventStore.Client/Interceptors/TypedExceptionInterceptor.cs @@ -1,3 +1,4 @@ + using System.Diagnostics.CodeAnalysis; using Grpc.Core; using Grpc.Core.Interceptors; @@ -13,11 +14,7 @@ class TypedExceptionInterceptor : Interceptor { }; public TypedExceptionInterceptor(Dictionary> customExceptionMap) { -#if NET - var map = new Dictionary>(DefaultExceptionMap.Concat(customExceptionMap)); -#else - var map = new Dictionary>(DefaultExceptionMap); -#endif + var map = new Dictionary>(DefaultExceptionMap.Concat(customExceptionMap)); ConvertRpcException = rpcEx => { if (rpcEx.TryMapException(map, out var ex)) diff --git a/test/Directory.Build.props b/test/Directory.Build.props index bb627a6db..2530c4364 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -1,57 +1,46 @@ - + - - true - xUnit1031 - + + true + xUnit1031 + - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers - - - - - - + + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - - + + + + + + + + + diff --git a/test/EventStore.Client.Operations.Tests/EventStore.Client.Operations.Tests.csproj b/test/EventStore.Client.Operations.Tests/EventStore.Client.Operations.Tests.csproj index e467be541..1febb6cfb 100644 --- a/test/EventStore.Client.Operations.Tests/EventStore.Client.Operations.Tests.csproj +++ b/test/EventStore.Client.Operations.Tests/EventStore.Client.Operations.Tests.csproj @@ -3,9 +3,4 @@ - - - - - diff --git a/test/EventStore.Client.PersistentSubscriptions.Tests/EventStore.Client.PersistentSubscriptions.Tests.csproj b/test/EventStore.Client.PersistentSubscriptions.Tests/EventStore.Client.PersistentSubscriptions.Tests.csproj index f035cbf98..562ccdc1c 100644 --- a/test/EventStore.Client.PersistentSubscriptions.Tests/EventStore.Client.PersistentSubscriptions.Tests.csproj +++ b/test/EventStore.Client.PersistentSubscriptions.Tests/EventStore.Client.PersistentSubscriptions.Tests.csproj @@ -1,19 +1,12 @@  - - - - - - - - - - - - - - - + + + + + + + + + - diff --git a/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/get_info.cs b/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/get_info.cs index 5e863562a..c7eca49a6 100644 --- a/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/get_info.cs +++ b/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/get_info.cs @@ -164,13 +164,14 @@ await Client.SubscribeToAllAsync( (s, e, r, ct) => { counter++; - if (counter == 1) - s.Nack(PersistentSubscriptionNakEventAction.Park, "Test", e); - - if (counter > 10) - tcs.TrySetResult(); - - return Task.CompletedTask; + switch (counter) { + case 1: s.Nack(PersistentSubscriptionNakEventAction.Park, "Test", e); + break; + case > 10: + tcs.TrySetResult(); + break; + } + return Task.CompletedTask; }, userCredentials: TestCredentials.Root ); diff --git a/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToStream/get_info.cs b/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToStream/get_info.cs index 40eee17b3..8eccd6678 100644 --- a/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToStream/get_info.cs +++ b/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToStream/get_info.cs @@ -171,13 +171,13 @@ await Client.SubscribeToStreamAsync( (s, e, r, ct) => { counter++; - if (counter == 1) - s.Nack(PersistentSubscriptionNakEventAction.Park, "Test", e); - - if (counter > 10) - tcs.TrySetResult(); - - return Task.CompletedTask; + switch (counter) { + case 1: s.Nack(PersistentSubscriptionNakEventAction.Park, "Test", e); + break; + case > 10: tcs.TrySetResult(); + break; + } + return Task.CompletedTask; }, userCredentials: TestCredentials.Root ); diff --git a/test/EventStore.Client.ProjectionManagement.Tests/EventStore.Client.ProjectionManagement.Tests.csproj b/test/EventStore.Client.ProjectionManagement.Tests/EventStore.Client.ProjectionManagement.Tests.csproj index 780d17aba..dac52c701 100644 --- a/test/EventStore.Client.ProjectionManagement.Tests/EventStore.Client.ProjectionManagement.Tests.csproj +++ b/test/EventStore.Client.ProjectionManagement.Tests/EventStore.Client.ProjectionManagement.Tests.csproj @@ -3,9 +3,4 @@ - - - - - \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/EventStore.Client.Streams.Tests.csproj b/test/EventStore.Client.Streams.Tests/EventStore.Client.Streams.Tests.csproj index 0e67ab47e..96cea21bb 100644 --- a/test/EventStore.Client.Streams.Tests/EventStore.Client.Streams.Tests.csproj +++ b/test/EventStore.Client.Streams.Tests/EventStore.Client.Streams.Tests.csproj @@ -6,10 +6,7 @@ - - - - - - + + + diff --git a/test/EventStore.Client.Streams.Tests/Read/EventBinaryData.cs b/test/EventStore.Client.Streams.Tests/Read/EventBinaryData.cs index 923a40cd1..780d016cb 100644 --- a/test/EventStore.Client.Streams.Tests/Read/EventBinaryData.cs +++ b/test/EventStore.Client.Streams.Tests/Read/EventBinaryData.cs @@ -6,7 +6,11 @@ public bool Equals(EventBinaryData other) => && Data.SequenceEqual(other.Data) && Metadata.SequenceEqual(other.Metadata); +#if NET public override int GetHashCode() => System.HashCode.Combine(Id, Data, Metadata); +#else + public override int GetHashCode() => HashCode.Combine(Id, Data, Metadata); +#endif } public static class EventBinaryDataConverters { @@ -30,4 +34,4 @@ public static EventBinaryData[] ToBinaryData(this IEnumerable sou public static ValueTask ToBinaryData(this IAsyncEnumerable source) => source.DefaultIfEmpty().Select(x => x.ToBinaryData()).ToArrayAsync(); -} \ No newline at end of file +} diff --git a/test/EventStore.Client.Tests.Common/ApplicationInfo.cs b/test/EventStore.Client.Tests.Common/ApplicationInfo.cs index 8a7a1ec7a..c55f90d50 100644 --- a/test/EventStore.Client.Tests.Common/ApplicationInfo.cs +++ b/test/EventStore.Client.Tests.Common/ApplicationInfo.cs @@ -23,11 +23,11 @@ static Application() { Environment = GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? Environments.Development; - var builder = new ConfigurationBuilder() - .AddJsonFile("appsettings.json", true) - .AddJsonFile($"appsettings.{Environment}.json", true) // Accept default naming convention - .AddJsonFile($"appsettings.{Environment.ToLowerInvariant()}.json", true) // Linux is case sensitive - .AddEnvironmentVariables(); + var builder = new ConfigurationBuilder() + .AddJsonFile("appsettings.json", true) + .AddJsonFile($"appsettings.{Environment}.json", true) // Accept default naming convention + .AddJsonFile($"appsettings.{Environment.ToLowerInvariant()}.json", true) // Linux is case sensitive + .AddEnvironmentVariables(); Configuration = builder.Build(); @@ -65,4 +65,4 @@ public static class OperatingSystem { public static bool IsMacOS() => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); public static bool IsLinux() => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); } -} \ No newline at end of file +} diff --git a/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj b/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj index 610243749..ba1a806f8 100644 --- a/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj +++ b/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj @@ -67,10 +67,4 @@ - - - - - - diff --git a/test/EventStore.Client.Tests.Common/EventStoreClientFixtureBase.cs b/test/EventStore.Client.Tests.Common/EventStoreClientFixtureBase.cs deleted file mode 100644 index b966e5098..000000000 --- a/test/EventStore.Client.Tests.Common/EventStoreClientFixtureBase.cs +++ /dev/null @@ -1,147 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Serilog; -using Serilog.Events; -using Serilog.Extensions.Logging; -using Serilog.Formatting.Display; -using Xunit; -using Xunit.Abstractions; - -namespace EventStore.Client { - public abstract class EventStoreClientFixtureBase : IAsyncLifetime { - public const string TestEventType = "-"; - - private const string ConnectionStringSingle = "esdb://localhost:2113/?tlsVerifyCert=false"; - private const string ConnectionStringCluster = "esdb://localhost:2113,localhost:2112,localhost:2111?tls=true&tlsVerifyCert=false"; - - private static readonly Subject LogEventSubject = new Subject(); - - private readonly IList _disposables; - public IEventStoreTestServer TestServer { get; } - protected EventStoreClientSettings Settings { get; } - - static EventStoreClientFixtureBase() { - ConfigureLogging(); - } - - private static void ConfigureLogging() { - var loggerConfiguration = new LoggerConfiguration() - .Enrich.FromLogContext() - .MinimumLevel.Is(LogEventLevel.Verbose) - .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) - .MinimumLevel.Override("Grpc", LogEventLevel.Verbose) - .WriteTo.Observers(observable => observable.Subscribe(LogEventSubject.OnNext)) - .WriteTo.Seq("http://localhost:5341/", period: TimeSpan.FromMilliseconds(1)); - Log.Logger = loggerConfiguration.CreateLogger(); - AppDomain.CurrentDomain.DomainUnload += (_, e) => Log.CloseAndFlush(); - } - - protected EventStoreClientFixtureBase(EventStoreClientSettings? clientSettings, - IDictionary? env = null) { - _disposables = new List(); - ServicePointManager.ServerCertificateValidationCallback = delegate { return true; }; - - var connectionString = GlobalEnvironment.UseCluster ? ConnectionStringCluster : ConnectionStringSingle; - Settings = clientSettings ?? EventStoreClientSettings.Create(connectionString); - - Settings.DefaultDeadline = Debugger.IsAttached - ? new TimeSpan?() - : TimeSpan.FromSeconds(30); - - var hostCertificatePath = Path.Combine(ProjectDir.Current, "..", "..", - GlobalEnvironment.UseCluster ? "certs-cluster" : "certs"); - - Settings.LoggerFactory ??= new SerilogLoggerFactory(); - - Settings.ConnectivitySettings.MaxDiscoverAttempts = 20; - Settings.ConnectivitySettings.DiscoveryInterval = TimeSpan.FromSeconds(1); - - if (GlobalEnvironment.UseExternalServer) { - TestServer = new EventStoreTestServerExternal(); - } else { - TestServer = GlobalEnvironment.UseCluster - ? new EventStoreTestServerCluster(hostCertificatePath, Settings.ConnectivitySettings.Address, env) - : new EventStoreTestServer(hostCertificatePath, Settings.ConnectivitySettings.Address, env); - } - } - - protected abstract Task OnServerUpAsync(); - protected abstract Task Given(); - protected abstract Task When(); - - public IEnumerable CreateTestEvents(int count = 1, string? type = null, int metadataSize = 1) - => Enumerable.Range(0, count).Select(index => CreateTestEvent(index, type ?? TestEventType, metadataSize)); - - protected static EventData CreateTestEvent(int index) => CreateTestEvent(index, TestEventType, 1); - - protected static EventData CreateTestEvent(int index, string type, int metadataSize) - => new EventData( - eventId: Uuid.NewUuid(), - type: type, - data: Encoding.UTF8.GetBytes($@"{{""x"":{index}}}"), - metadata: Encoding.UTF8.GetBytes("\"" + new string('$', metadataSize) + "\"")); - - public virtual async Task InitializeAsync() { - await TestServer.StartAsync().WithTimeout(TimeSpan.FromMinutes(5)); - await OnServerUpAsync().WithTimeout(TimeSpan.FromMinutes(5)); - await Given().WithTimeout(TimeSpan.FromMinutes(5)); - await When().WithTimeout(TimeSpan.FromMinutes(5)); - } - - public virtual Task DisposeAsync() { - foreach (var disposable in _disposables) { - disposable.Dispose(); - } - - return TestServer.DisposeAsync().AsTask().WithTimeout(TimeSpan.FromMinutes(5)); - } - - public string GetStreamName([CallerMemberName] string? testMethod = null) { - var type = GetType(); - - return $"{type.DeclaringType?.Name}.{testMethod ?? "unknown"}"; - } - - public void CaptureLogs(ITestOutputHelper testOutputHelper) { - const string captureCorrelationId = nameof(captureCorrelationId); - - var captureId = Guid.NewGuid(); - - var callContextData = new AsyncLocal<(string, Guid)> { - Value = (captureCorrelationId, captureId) - }; - - bool Filter(LogEvent logEvent) => callContextData.Value.Item2.Equals(captureId); - - MessageTemplateTextFormatter formatter = new MessageTemplateTextFormatter( - "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] [{SourceContext}] {Message}"); - - MessageTemplateTextFormatter formatterWithException = - new MessageTemplateTextFormatter( - "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] [{SourceContext}] {Message}{NewLine}{Exception}"); - - var subscription = LogEventSubject.Where(Filter).Subscribe(logEvent => { - using var writer = new StringWriter(); - if (logEvent.Exception != null) { - formatterWithException.Format(logEvent, writer); - } else { - formatter.Format(logEvent, writer); - } - - testOutputHelper.WriteLine(writer.ToString()); - }); - - _disposables.Add(subscription); - } - } -} diff --git a/test/EventStore.Client.Tests.Common/EventStoreTestServer.cs b/test/EventStore.Client.Tests.Common/EventStoreTestServer.cs deleted file mode 100644 index c6e30368b..000000000 --- a/test/EventStore.Client.Tests.Common/EventStoreTestServer.cs +++ /dev/null @@ -1,139 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Ductus.FluentDocker.Builders; -using Ductus.FluentDocker.Extensions; -using Ductus.FluentDocker.Model.Builders; -using Ductus.FluentDocker.Services; -using Ductus.FluentDocker.Services.Extensions; -using Polly; - -namespace EventStore.Client { - public class EventStoreTestServer : IEventStoreTestServer { - private readonly string _hostCertificatePath; - private readonly IContainerService _eventStore; - private readonly HttpClient _httpClient; - private static readonly string ContainerName = "es-client-dotnet-test"; - - private static Version? _version; - public static Version Version => _version ??= GetVersion(); - - private static Version GetVersion() { - const string versionPrefix = "EventStoreDB version"; - - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); - using var eventstore = new Builder().UseContainer() - .UseImage(GlobalEnvironment.DockerImage) - .Command("--version") - .Build() - .Start(); - using var log = eventstore.Logs(true, cts.Token); - foreach (var line in log.ReadToEnd()) { - if (line.StartsWith(versionPrefix) && - Version.TryParse(line[(versionPrefix.Length + 1)..].Split(' ')[0], out var version)) { - return version; - } - } - - throw new InvalidOperationException("Could not determine server version."); - } - - public EventStoreTestServer( - string hostCertificatePath, - Uri address, - IDictionary? envOverrides) { - - _hostCertificatePath = hostCertificatePath; - VerifyCertificatesExist(); - -#if NET - _httpClient = new HttpClient(new SocketsHttpHandler { - SslOptions = {RemoteCertificateValidationCallback = delegate { return true; }} - }) { - BaseAddress = address, - }; -#else - _httpClient = new HttpClient(new WinHttpHandler { - ServerCertificateValidationCallback = delegate { return true; } - }) { - BaseAddress = address, - }; -#endif - - var env = new Dictionary { - ["EVENTSTORE_DB_LOG_FORMAT"] = GlobalEnvironment.DbLogFormat, - ["EVENTSTORE_MEM_DB"] = "true", - ["EVENTSTORE_CERTIFICATE_FILE"] = "/etc/eventstore/certs/node/node.crt", - ["EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE"] = "/etc/eventstore/certs/node/node.key", - ["EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH"] = "/etc/eventstore/certs/ca", - ["EVENTSTORE_LOG_LEVEL"] = "Verbose", - ["EVENTSTORE_STREAM_EXISTENCE_FILTER_SIZE"] = "10000", - ["EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP"] = "True" - }; - foreach (var val in envOverrides ?? Enumerable.Empty>()) { - env[val.Key] = val.Value; - } - - _eventStore = new Builder() - .UseContainer() - .UseImage(GlobalEnvironment.DockerImage) - .WithEnvironment(env.Select(pair => $"{pair.Key}={pair.Value}").ToArray()) - .WithName(ContainerName) - .MountVolume(_hostCertificatePath, "/etc/eventstore/certs", MountType.ReadOnly) - .ExposePort(2113, 2113) - //.KeepContainer() - //.KeepRunning() - .Build(); - } - - - private void VerifyCertificatesExist() { - var certificateFiles = new[] { - Path.Combine("ca", "ca.crt"), - Path.Combine("ca", "ca.key"), - Path.Combine("node", "node.crt"), - Path.Combine("node", "node.key") - }.Select(path => Path.Combine(_hostCertificatePath, path)); - - foreach (var file in certificateFiles) { - if (!File.Exists(file)) { - throw new InvalidOperationException( - $"Could not locate the certificates file {file} needed to run EventStoreDB. Please run the 'gencert' tool at the root of the repository."); - } - } - } - - public async Task StartAsync(CancellationToken cancellationToken = default) { - _eventStore.Start(); - try { - await Policy.Handle() - .WaitAndRetryAsync(200, retryCount => TimeSpan.FromMilliseconds(100)) - .ExecuteAsync(async () => { - using var response = await _httpClient.GetAsync("/health/live", cancellationToken); - if (response.StatusCode >= HttpStatusCode.BadRequest) { - throw new Exception($"Health check failed with status code: {response.StatusCode}."); - } - }); - } catch (Exception) { - _eventStore.Dispose(); - throw; - } - } - - public void Stop() { - _eventStore.Stop(); - } - - public ValueTask DisposeAsync() { - _httpClient?.Dispose(); - _eventStore?.Dispose(); - - return new ValueTask(Task.CompletedTask); - } - } -} diff --git a/test/EventStore.Client.Tests.Common/EventStoreTestServerCluster.cs b/test/EventStore.Client.Tests.Common/EventStoreTestServerCluster.cs deleted file mode 100644 index 43dce3c2e..000000000 --- a/test/EventStore.Client.Tests.Common/EventStoreTestServerCluster.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Ductus.FluentDocker.Builders; -using Ductus.FluentDocker.Common; -using Ductus.FluentDocker.Services; -using Polly; - -namespace EventStore.Client { - public class EventStoreTestServerCluster : IEventStoreTestServer { - private readonly ICompositeService _eventStoreCluster; - private readonly HttpClient _httpClient; - - public EventStoreTestServerCluster( - string hostCertificatePath, - Uri address, - IDictionary? envOverrides) { - - envOverrides ??= new Dictionary(); - envOverrides["ES_CERTS_CLUSTER"] = hostCertificatePath; - - _eventStoreCluster = BuildCluster(envOverrides); - -#if NET - _httpClient = new HttpClient(new SocketsHttpHandler { - SslOptions = {RemoteCertificateValidationCallback = delegate { return true; }} - }) { - BaseAddress = address, - }; -#else - _httpClient = new HttpClient(new WinHttpHandler { - ServerCertificateValidationCallback = delegate { return true; } - }) { - BaseAddress = address, - }; -#endif - } - - private ICompositeService BuildCluster(IDictionary? envOverrides = null) { - var env = GlobalEnvironment.EnvironmentVariables(envOverrides); - return new Builder() - .UseContainer() - .UseCompose() - .WithEnvironment(env.Select(pair => $"{pair.Key}={pair.Value}").ToArray()) - .FromFile("docker-compose.yml") - .ForceRecreate() - .RemoveOrphans() - .Build(); - } - - public async Task StartAsync(CancellationToken cancellationToken = default) { - try { - // don't know why, sometimes the default network (e.g. net50_default) remains - // from previous cluster and prevents docker-compose up from executing successfully - Policy.Handle() - .WaitAndRetry( - retryCount: 10, - sleepDurationProvider: retryCount => TimeSpan.FromSeconds(2), - onRetry: (ex, _) => { - BuildCluster().Dispose(); - _eventStoreCluster.Start(); - }) - .Execute(() => { - _eventStoreCluster.Start(); - }); - - await Policy.Handle() - .WaitAndRetryAsync(200, retryCount => TimeSpan.FromMilliseconds(100)) - .ExecuteAsync(async () => { - using var response = await _httpClient.GetAsync("/health/live", cancellationToken); - if (response.StatusCode >= HttpStatusCode.BadRequest) { - throw new Exception($"Health check failed with status code: {response.StatusCode}."); - } - }); - } catch (Exception) { - _eventStoreCluster.Dispose(); - throw; - } - } - - public void Stop() { - _eventStoreCluster.Stop(); - } - - public ValueTask DisposeAsync() { - _eventStoreCluster.Dispose(); - return new ValueTask(Task.CompletedTask); - } - } -} diff --git a/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServer.cs b/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServer.cs index 032fa278e..8098f4974 100644 --- a/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServer.cs +++ b/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServer.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Net.Http; using Ductus.FluentDocker.Builders; using Ductus.FluentDocker.Extensions; using Ductus.FluentDocker.Model.Builders; @@ -9,29 +10,27 @@ namespace EventStore.Client.Tests; public class EventStoreTestServer : IEventStoreTestServer { - static readonly string ContainerName = "es-client-dotnet-test"; + static readonly string ContainerName = "es-client-dotnet-test"; - static Version? _version; - readonly IContainerService _eventStore; - readonly string _hostCertificatePath; - readonly HttpClient _httpClient; + static Version? _version; + readonly IContainerService _eventStore; + readonly string _hostCertificatePath; + readonly HttpClient _httpClient; - public EventStoreTestServer( - string hostCertificatePath, - Uri address, - IDictionary? envOverrides - ) { - _hostCertificatePath = hostCertificatePath; - VerifyCertificatesExist(); + public EventStoreTestServer( + string hostCertificatePath, + Uri address, + IDictionary? envOverrides + ) { + _hostCertificatePath = hostCertificatePath; + VerifyCertificatesExist(); #if NET - _httpClient = new( - new SocketsHttpHandler { - SslOptions = { RemoteCertificateValidationCallback = delegate { return true; } } - } - ) { - BaseAddress = address - }; + _httpClient = new HttpClient(new SocketsHttpHandler { + SslOptions = {RemoteCertificateValidationCallback = delegate { return true; }} + }) { + BaseAddress = address, + }; #else _httpClient = new HttpClient(new WinHttpHandler { ServerCertificateValidationCallback = delegate { return true; } @@ -40,97 +39,98 @@ public EventStoreTestServer( }; #endif - var env = new Dictionary { - ["EVENTSTORE_DB_LOG_FORMAT"] = "V2", - ["EVENTSTORE_MEM_DB"] = "true", - ["EVENTSTORE_CHUNK_SIZE"] = (1024 * 1024).ToString(), - ["EVENTSTORE_CERTIFICATE_FILE"] = "/etc/eventstore/certs/node/node.crt", - ["EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE"] = "/etc/eventstore/certs/node/node.key", - ["EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH"] = "/etc/eventstore/certs/ca", - ["EVENTSTORE_LOG_LEVEL"] = "Verbose", - ["EVENTSTORE_STREAM_EXISTENCE_FILTER_SIZE"] = "10000", - ["EVENTSTORE_STREAM_INFO_CACHE_CAPACITY"] = "10000", - ["EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP"] = "false", - ["EVENTSTORE_DISABLE_LOG_FILE"] = "true" - }; - - foreach (var (key, value) in envOverrides ?? Enumerable.Empty>()) - env[key] = value; - - _eventStore = new Builder() - .UseContainer() - .UseImage(GlobalEnvironment.DockerImage) - .WithEnvironment(env.Select(pair => $"{pair.Key}={pair.Value}").ToArray()) - .WithName(ContainerName) - .MountVolume(_hostCertificatePath, "/etc/eventstore/certs", MountType.ReadOnly) - .ExposePort(2113, 2113) - //.WaitForHealthy(TimeSpan.FromSeconds(120)) - //.KeepContainer() - //.KeepRunning() - .Build(); - } + var env = new Dictionary { + ["EVENTSTORE_DB_LOG_FORMAT"] = "V2", + ["EVENTSTORE_MEM_DB"] = "true", + ["EVENTSTORE_CHUNK_SIZE"] = (1024 * 1024).ToString(), + ["EVENTSTORE_CERTIFICATE_FILE"] = "/etc/eventstore/certs/node/node.crt", + ["EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE"] = "/etc/eventstore/certs/node/node.key", + ["EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH"] = "/etc/eventstore/certs/ca", + ["EVENTSTORE_LOG_LEVEL"] = "Verbose", + ["EVENTSTORE_STREAM_EXISTENCE_FILTER_SIZE"] = "10000", + ["EVENTSTORE_STREAM_INFO_CACHE_CAPACITY"] = "10000", + ["EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP"] = "false", + ["EVENTSTORE_DISABLE_LOG_FILE"] = "true" + }; - public static Version Version => _version ??= GetVersion(); - - public async Task StartAsync(CancellationToken cancellationToken = default) { - _eventStore.Start(); - try { - await Policy.Handle() - .WaitAndRetryAsync(200, retryCount => TimeSpan.FromMilliseconds(100)) - .ExecuteAsync( - async () => { - using var response = await _httpClient.GetAsync("/health/live", cancellationToken); - if (response.StatusCode >= HttpStatusCode.BadRequest) - throw new($"Health check failed with status code: {response.StatusCode}."); - } - ); - } - catch (Exception) { - _eventStore.Dispose(); - throw; + foreach (var val in envOverrides ?? Enumerable.Empty>()) { + env[val.Key] = val.Value; } - } - - public void Stop() => _eventStore.Stop(); - - public ValueTask DisposeAsync() { - _httpClient?.Dispose(); - _eventStore?.Dispose(); - return ValueTask.CompletedTask; + _eventStore = new Builder() + .UseContainer() + .UseImage(GlobalEnvironment.DockerImage) + .WithEnvironment(env.Select(pair => $"{pair.Key}={pair.Value}").ToArray()) + .WithName(ContainerName) + .MountVolume(_hostCertificatePath, "/etc/eventstore/certs", MountType.ReadOnly) + .ExposePort(2113, 2113) + //.WaitForHealthy(TimeSpan.FromSeconds(120)) + //.KeepContainer() + //.KeepRunning() + .Build(); + } + + public static Version Version => _version ??= GetVersion(); + + public async Task StartAsync(CancellationToken cancellationToken = default) { + _eventStore.Start(); + try { + await Policy.Handle() + .WaitAndRetryAsync(200, retryCount => TimeSpan.FromMilliseconds(100)) + .ExecuteAsync( + async () => { + using var response = await _httpClient.GetAsync("/health/live", cancellationToken); + if (response.StatusCode >= HttpStatusCode.BadRequest) + throw new($"Health check failed with status code: {response.StatusCode}."); + } + ); + } + catch (Exception) { + _eventStore.Dispose(); + throw; + } + } + + public void Stop() => _eventStore.Stop(); + + public ValueTask DisposeAsync() { + _httpClient?.Dispose(); + _eventStore?.Dispose(); + + return new ValueTask(Task.CompletedTask); } - static Version GetVersion() { - const string versionPrefix = "EventStoreDB version"; - - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); - using var eventstore = new Builder().UseContainer() - .UseImage(GlobalEnvironment.DockerImage) - .Command("--version") - .Build() - .Start(); - - using var log = eventstore.Logs(true, cts.Token); - foreach (var line in log.ReadToEnd()) - if (line.StartsWith(versionPrefix) && - Version.TryParse(line[(versionPrefix.Length + 1)..].Split(' ')[0], out var version)) - return version; - - throw new InvalidOperationException("Could not determine server version."); - } - - void VerifyCertificatesExist() { - var certificateFiles = new[] { - Path.Combine("ca", "ca.crt"), - Path.Combine("ca", "ca.key"), - Path.Combine("node", "node.crt"), - Path.Combine("node", "node.key") - }.Select(path => Path.Combine(_hostCertificatePath, path)); - - foreach (var file in certificateFiles) - if (!File.Exists(file)) - throw new InvalidOperationException( - $"Could not locate the certificates file {file} needed to run EventStoreDB. Please run the 'gencert' tool at the root of the repository." - ); - } + static Version GetVersion() { + const string versionPrefix = "EventStoreDB version"; + + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + using var eventstore = new Builder().UseContainer() + .UseImage(GlobalEnvironment.DockerImage) + .Command("--version") + .Build() + .Start(); + + using var log = eventstore.Logs(true, cts.Token); + foreach (var line in log.ReadToEnd()) + if (line.StartsWith(versionPrefix) && + Version.TryParse(line[(versionPrefix.Length + 1)..].Split(' ')[0], out var version)) + return version; + + throw new InvalidOperationException("Could not determine server version."); + } + + void VerifyCertificatesExist() { + var certificateFiles = new[] { + Path.Combine("ca", "ca.crt"), + Path.Combine("ca", "ca.key"), + Path.Combine("node", "node.crt"), + Path.Combine("node", "node.key") + }.Select(path => Path.Combine(_hostCertificatePath, path)); + + foreach (var file in certificateFiles) + if (!File.Exists(file)) + throw new InvalidOperationException( + $"Could not locate the certificates file {file} needed to run EventStoreDB. Please run the 'gencert' tool at the root of the repository." + ); + } } diff --git a/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServerCluster.cs b/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServerCluster.cs index 5e7155df4..67ca48d31 100644 --- a/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServerCluster.cs +++ b/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServerCluster.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Net.Http; using Ductus.FluentDocker.Builders; using Ductus.FluentDocker.Common; using Ductus.FluentDocker.Services; @@ -22,13 +23,11 @@ public EventStoreTestServerCluster( _eventStoreCluster = BuildCluster(envOverrides); #if NET - _httpClient = new( - new SocketsHttpHandler { - SslOptions = { RemoteCertificateValidationCallback = delegate { return true; } } - } - ) { - BaseAddress = address - }; + _httpClient = new HttpClient(new SocketsHttpHandler { + SslOptions = {RemoteCertificateValidationCallback = delegate { return true; }} + }) { + BaseAddress = address, + }; #else _httpClient = new HttpClient(new WinHttpHandler { ServerCertificateValidationCallback = delegate { return true; } diff --git a/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServerExternal.cs b/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServerExternal.cs index 1b6ff3492..19b866a63 100644 --- a/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServerExternal.cs +++ b/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServerExternal.cs @@ -4,5 +4,5 @@ public class EventStoreTestServerExternal : IEventStoreTestServer { public Task StartAsync(CancellationToken cancellationToken = default) => Task.CompletedTask; public void Stop() { } - public ValueTask DisposeAsync() => ValueTask.CompletedTask; -} \ No newline at end of file + public ValueTask DisposeAsync() => new ValueTask(Task.CompletedTask); +} diff --git a/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestNode.cs b/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestNode.cs index 46ae316da..97dc3725e 100644 --- a/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestNode.cs +++ b/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestNode.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Net.Http; using System.Net.Sockets; using Ductus.FluentDocker.Builders; using Ductus.FluentDocker.Common; @@ -82,7 +83,11 @@ protected override ContainerBuilder Configure() { protected override async Task OnServiceStarted() { using var http = new HttpClient( +#if NET new SocketsHttpHandler { SslOptions = { RemoteCertificateValidationCallback = delegate { return true; } } } +#else + new WinHttpHandler { ServerCertificateValidationCallback = delegate { return true; } } +#endif ) { BaseAddress = Options.ClientSettings.ConnectivitySettings.Address }; @@ -132,7 +137,11 @@ public async Task GetNextAvailablePort(TimeSpan delay = default) { await Task.Delay(delay); } finally { +#if NET if (socket.Connected) await socket.DisconnectAsync(true); +#else + if (socket.Connected) socket.Disconnect(true); +#endif } } } @@ -142,4 +151,4 @@ public async Task GetNextAvailablePort(TimeSpan delay = default) { } public int NextAvailablePort => GetNextAvailablePort(FromMilliseconds(100)).GetAwaiter().GetResult(); -} \ No newline at end of file +} diff --git a/test/EventStore.Client.Tests.Common/FluentDocker/FluentDockerServiceExtensions.cs b/test/EventStore.Client.Tests.Common/FluentDocker/FluentDockerServiceExtensions.cs index e763edd35..2e2f74fc3 100644 --- a/test/EventStore.Client.Tests.Common/FluentDocker/FluentDockerServiceExtensions.cs +++ b/test/EventStore.Client.Tests.Common/FluentDocker/FluentDockerServiceExtensions.cs @@ -27,19 +27,55 @@ public static async ValueTask WaitUntilNodesAreHealthy(this IContainerService se await WaitUntilNodesAreHealthy(service, cts.Token); } - public static async Task WaitUntilNodesAreHealthy(this ICompositeService service, IEnumerable services, CancellationToken cancellationToken) { +#if NET + public static async Task WaitUntilNodesAreHealthy(this ICompositeService service, IEnumerable services, CancellationToken cancellationToken) +#else + public static void WaitUntilNodesAreHealthy(this ICompositeService service, IEnumerable services, CancellationToken cancellationToken) +#endif +{ var nodes = service.Containers.Where(x => services.Contains(x.Name)); - + +#if NET await Parallel.ForEachAsync(nodes, cancellationToken, async (node, ct) => await node.WaitUntilNodesAreHealthy(ct)); - } +#else + Parallel.ForEach( + nodes, + node => { + node.WaitUntilNodesAreHealthy(cancellationToken).GetAwaiter().GetResult(); + } + ); +#endif + } - public static async Task WaitUntilNodesAreHealthy(this ICompositeService service, string serviceNamePrefix, CancellationToken cancellationToken) { +#if NET + public static async Task WaitUntilNodesAreHealthy(this ICompositeService service, string serviceNamePrefix, CancellationToken cancellationToken) +#else + public static void WaitUntilNodesAreHealthy(this ICompositeService service, string serviceNamePrefix, CancellationToken cancellationToken) +#endif +{ var nodes = service.Containers.Where(x => x.Name.StartsWith(serviceNamePrefix)); +#if NET await Parallel.ForEachAsync(nodes, cancellationToken, async (node, ct) => await node.WaitUntilNodesAreHealthy(ct)); +#else + Parallel.ForEach( + nodes, + node => { + node.WaitUntilNodesAreHealthy(cancellationToken).GetAwaiter().GetResult(); + } + ); +#endif } +#if NET public static async Task WaitUntilNodesAreHealthy(this ICompositeService service, string serviceNamePrefix, TimeSpan timeout) { +#else + public static void WaitUntilNodesAreHealthy(this ICompositeService service, string serviceNamePrefix, TimeSpan timeout) { +#endif using var cts = new CancellationTokenSource(timeout); +#if NET await WaitUntilNodesAreHealthy(service, serviceNamePrefix, cts.Token); +#else + WaitUntilNodesAreHealthy(service, serviceNamePrefix, cts.Token); +#endif } -} \ No newline at end of file +} diff --git a/test/EventStore.Client.Tests.Common/GlobalEnvironment.cs b/test/EventStore.Client.Tests.Common/GlobalEnvironment.cs index f4bf8e633..74f8838da 100644 --- a/test/EventStore.Client.Tests.Common/GlobalEnvironment.cs +++ b/test/EventStore.Client.Tests.Common/GlobalEnvironment.cs @@ -33,7 +33,7 @@ static void EnsureDefaults(IConfiguration configuration) { configuration.EnsureValue("EVENTSTORE_DISABLE_LOG_FILE", "true"); configuration.EnsureValue("EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH", "/etc/eventstore/certs/ca"); configuration.EnsureValue("EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP", "true"); - + } } diff --git a/test/EventStore.Client.Tests/ConnectionStringTests.cs b/test/EventStore.Client.Tests/ConnectionStringTests.cs index 2edb9f547..96a5a4eff 100644 --- a/test/EventStore.Client.Tests/ConnectionStringTests.cs +++ b/test/EventStore.Client.Tests/ConnectionStringTests.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Net.Http; using AutoFixture; namespace EventStore.Client.Tests; @@ -105,6 +106,7 @@ public void valid_connection_string_with_empty_path(string connectionString, Eve Assert.Equal(expected, result, EventStoreClientSettingsEqualityComparer.Instance); } +#if !GRPC_CORE [Theory] [InlineData(false)] [InlineData(true)] @@ -140,6 +142,8 @@ public void tls_verify_cert(bool tlsVerifyCert) { #endif } +#endif + [Fact] public void infinite_grpc_timeouts() { var result = EventStoreClientSettings.Create("esdb://localhost:2113?keepAliveInterval=-1&keepAliveTimeout=-1"); @@ -148,11 +152,15 @@ public void infinite_grpc_timeouts() { Assert.Equal(System.Threading.Timeout.InfiniteTimeSpan, result.ConnectivitySettings.KeepAliveTimeout); using var handler = result.CreateHttpMessageHandler?.Invoke(); - +#if NET var socketsHandler = Assert.IsType(handler); - Assert.Equal(System.Threading.Timeout.InfiniteTimeSpan, socketsHandler.KeepAlivePingTimeout); Assert.Equal(System.Threading.Timeout.InfiniteTimeSpan, socketsHandler.KeepAlivePingDelay); +#else + var winHttpHandler = Assert.IsType(handler); + Assert.Equal(System.Threading.Timeout.InfiniteTimeSpan, winHttpHandler.TcpKeepAliveTime); + Assert.Equal(System.Threading.Timeout.InfiniteTimeSpan, winHttpHandler.TcpKeepAliveInterval); +#endif } [Fact] @@ -368,14 +376,19 @@ static string GetKeyValuePairs( settings.DefaultDeadline.Value.TotalMilliseconds.ToString() ); -#if !GRPC_CORE if (settings.CreateHttpMessageHandler != null) { using var handler = settings.CreateHttpMessageHandler.Invoke(); +#if NET if (handler is SocketsHttpHandler socketsHttpHandler && socketsHttpHandler.SslOptions.RemoteCertificateValidationCallback != null) pairs.Add("tlsVerifyCert", "false"); - } +#else + if (handler is WinHttpHandler winHttpHandler && + winHttpHandler.ServerCertificateValidationCallback != null) { + pairs.Add("tlsVerifyCert", "false"); + } #endif + } return string.Join("&", pairs.Select(pair => $"{getKey?.Invoke(pair.Key) ?? pair.Key}={pair.Value}")); } diff --git a/test/EventStore.Client.Tests/EventStore.Client.Tests.csproj b/test/EventStore.Client.Tests/EventStore.Client.Tests.csproj index 56cd54ef6..2ea70a0e2 100644 --- a/test/EventStore.Client.Tests/EventStore.Client.Tests.csproj +++ b/test/EventStore.Client.Tests/EventStore.Client.Tests.csproj @@ -3,9 +3,19 @@ + + + + + + + + + + @@ -15,17 +25,10 @@ - - - - - - - - - - - + + + + diff --git a/test/EventStore.Client.UserManagement.Tests/EventStore.Client.UserManagement.Tests.csproj b/test/EventStore.Client.UserManagement.Tests/EventStore.Client.UserManagement.Tests.csproj index 8a13ee09e..d4b52e67f 100644 --- a/test/EventStore.Client.UserManagement.Tests/EventStore.Client.UserManagement.Tests.csproj +++ b/test/EventStore.Client.UserManagement.Tests/EventStore.Client.UserManagement.Tests.csproj @@ -9,9 +9,4 @@ - - - - - \ No newline at end of file From bbfcad908d65324c226f8243108d6d864cc10745 Mon Sep 17 00:00:00 2001 From: William Chong Date: Mon, 15 Jan 2024 10:05:57 +0400 Subject: [PATCH 23/41] Fix more errors --- .../Interceptors/TypedExceptionInterceptor.cs | 17 ++++++++++ .../Fixtures/EventStoreTestCluster.cs | 13 +++++--- .../FluentDocker/TestBypassService.cs | 10 ++++-- .../FluentDocker/TestService.cs | 31 +++++++++++++++---- 4 files changed, 59 insertions(+), 12 deletions(-) diff --git a/src/EventStore.Client/Interceptors/TypedExceptionInterceptor.cs b/src/EventStore.Client/Interceptors/TypedExceptionInterceptor.cs index 75754f95f..0781eb9ef 100644 --- a/src/EventStore.Client/Interceptors/TypedExceptionInterceptor.cs +++ b/src/EventStore.Client/Interceptors/TypedExceptionInterceptor.cs @@ -13,8 +13,13 @@ class TypedExceptionInterceptor : Interceptor { [Exceptions.NotLeader] = ex => ex.ToNotLeaderException(), }; +#if NET48 + public TypedExceptionInterceptor(Dictionary> customExceptionMap) { + var map = new Dictionary>(DefaultExceptionMap); +#else public TypedExceptionInterceptor(Dictionary> customExceptionMap) { var map = new Dictionary>(DefaultExceptionMap.Concat(customExceptionMap)); +#endif ConvertRpcException = rpcEx => { if (rpcEx.TryMapException(map, out var ex)) @@ -117,6 +122,17 @@ public static NotAuthenticatedException ToNotAuthenticatedException(this RpcExce public static RpcException ToDeadlineExceededRpcException(this RpcException exception) => new(new Status(DeadlineExceeded, exception.Status.Detail, exception.Status.DebugException)); +#if NET48 + public static bool TryMapException(this RpcException exception, Dictionary> map, out Exception createdException) { + if (exception.Trailers.TryGetValue(Exceptions.ExceptionKey, out var key) && map.TryGetValue(key!, out var factory)) { + createdException = factory.Invoke(exception); + return true; + } + + createdException = null!; + return false; + } +#else public static bool TryMapException(this RpcException exception, Dictionary> map, [MaybeNullWhen(false)] out Exception createdException) { if (exception.Trailers.TryGetValue(Exceptions.ExceptionKey, out var key) && map.TryGetValue(key!, out var factory)) { createdException = factory.Invoke(exception); @@ -126,6 +142,7 @@ public static bool TryMapException(this RpcException exception, Dictionary(IAsyncStreamReader reader, Func convertException) : IAsyncStreamReader { diff --git a/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestCluster.cs b/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestCluster.cs index f97b852e2..5c366e8d0 100644 --- a/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestCluster.cs +++ b/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestCluster.cs @@ -47,7 +47,12 @@ protected override CompositeBuilder Configure() { return builder; } - protected override async Task OnServiceStarted() { - await Service.WaitUntilNodesAreHealthy("esdb-node", TimeSpan.FromSeconds(60)); - } -} \ No newline at end of file + protected override async Task OnServiceStarted() { + #if NET48 + Service.WaitUntilNodesAreHealthy("esdb-node", TimeSpan.FromSeconds(60)); + #else + await Service.WaitUntilNodesAreHealthy("esdb-node", TimeSpan.FromSeconds(60)); + #endif + await Task.CompletedTask; + } +} diff --git a/test/EventStore.Client.Tests.Common/FluentDocker/TestBypassService.cs b/test/EventStore.Client.Tests.Common/FluentDocker/TestBypassService.cs index 2fb3e805c..144f73d5f 100644 --- a/test/EventStore.Client.Tests.Common/FluentDocker/TestBypassService.cs +++ b/test/EventStore.Client.Tests.Common/FluentDocker/TestBypassService.cs @@ -25,7 +25,13 @@ public override async Task Stop() { } } - public override ValueTask DisposeAsync() => ValueTask.CompletedTask; + public override ValueTask DisposeAsync() { +#if NET + return ValueTask.CompletedTask; +#else + return new ValueTask(); +#endif + } } public sealed class BypassService : IService { @@ -58,4 +64,4 @@ public BypassBuilder() : this(null) { } public override BypassService Build() => new BypassService(); protected override IBuilder InternalCreate() => this; -} \ No newline at end of file +} diff --git a/test/EventStore.Client.Tests.Common/FluentDocker/TestService.cs b/test/EventStore.Client.Tests.Common/FluentDocker/TestService.cs index 026e0a12c..5816c2a3d 100644 --- a/test/EventStore.Client.Tests.Common/FluentDocker/TestService.cs +++ b/test/EventStore.Client.Tests.Common/FluentDocker/TestService.cs @@ -1,6 +1,7 @@ using Ductus.FluentDocker.Builders; using Ductus.FluentDocker.Common; using Ductus.FluentDocker.Services; +using Google.Protobuf.WellKnownTypes; using Serilog; using static Serilog.Core.Constants; @@ -10,10 +11,14 @@ public interface ITestService : IAsyncDisposable { Task Start(); Task Stop(); Task Restart(TimeSpan delay); - - Task Restart() => Restart(TimeSpan.Zero); - - void ReportStatus(); + +#if NET + Task Restart() => Restart(TimeSpan.Zero); +#else + Task Restart(); +#endif + + void ReportStatus(); } public abstract class TestService : ITestService where TService : IService where TBuilder : BaseBuilder { @@ -25,6 +30,13 @@ public abstract class TestService : ITestService where TServ INetworkService? Network { get; set; } = null!; +#if !NET + Task ITestService.Restart() + { + return Restart(TimeSpan.Zero); + } +#endif + public virtual async Task Start() { Logger.Information("Container service starting"); @@ -63,7 +75,7 @@ public virtual async Task Stop() { throw new FluentDockerException("Failed to stop container service", ex); } } - + public virtual async Task Restart(TimeSpan delay) { try { try { @@ -117,6 +129,12 @@ void ReportContainerStatus(IContainerService service) { } } +#if !NET + public virtual ValueTask DisposeAsync() + { + return DisposeAsync(); + } +#else public virtual ValueTask DisposeAsync() { try { Network?.Dispose(); @@ -134,9 +152,10 @@ public virtual ValueTask DisposeAsync() { return ValueTask.CompletedTask; } +#endif protected abstract TBuilder Configure(); protected virtual Task OnServiceStarted() => Task.CompletedTask; protected virtual Task OnServiceStop() => Task.CompletedTask; -} \ No newline at end of file +} From 5f6e03bb079ba0fc1b2984ea93ca16a8009c9cf0 Mon Sep 17 00:00:00 2001 From: William Chong Date: Mon, 15 Jan 2024 10:15:10 +0400 Subject: [PATCH 24/41] Remove duplicates in solution --- test/Directory.Build.props | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 2530c4364..abbb02e16 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -17,8 +17,6 @@ - - @@ -31,10 +29,6 @@ - - - - From 0df0e7ea774cfecf8950a430c08608eb2aff1589 Mon Sep 17 00:00:00 2001 From: William Chong Date: Mon, 15 Jan 2024 12:18:13 +0400 Subject: [PATCH 25/41] Remove duplicates --- samples/Directory.Build.props | 2 +- src/EventStore.Client.Streams/EventStore.Client.Streams.csproj | 2 +- test/EventStore.Client.Tests/EventStore.Client.Tests.csproj | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/samples/Directory.Build.props b/samples/Directory.Build.props index 2be8b7798..4e510486d 100644 --- a/samples/Directory.Build.props +++ b/samples/Directory.Build.props @@ -1,6 +1,6 @@ - net48;net8.0 + net8.0 enable enable true diff --git a/src/EventStore.Client.Streams/EventStore.Client.Streams.csproj b/src/EventStore.Client.Streams/EventStore.Client.Streams.csproj index 6d85d65a9..3c541daff 100644 --- a/src/EventStore.Client.Streams/EventStore.Client.Streams.csproj +++ b/src/EventStore.Client.Streams/EventStore.Client.Streams.csproj @@ -6,7 +6,7 @@ - + diff --git a/test/EventStore.Client.Tests/EventStore.Client.Tests.csproj b/test/EventStore.Client.Tests/EventStore.Client.Tests.csproj index 2ea70a0e2..23abbbf34 100644 --- a/test/EventStore.Client.Tests/EventStore.Client.Tests.csproj +++ b/test/EventStore.Client.Tests/EventStore.Client.Tests.csproj @@ -28,7 +28,6 @@ - From e624b94bd7d307eb0ae6de4a2805950f4e21d15c Mon Sep 17 00:00:00 2001 From: William Chong Date: Wed, 17 Jan 2024 18:22:16 +0400 Subject: [PATCH 26/41] Fix compile errors --- test/Directory.Build.props | 7 +------ .../EventStore.Client.Tests.Common.csproj | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/test/Directory.Build.props b/test/Directory.Build.props index abbb02e16..796b535a3 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -29,12 +29,7 @@ - - + - diff --git a/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj b/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj index ba1a806f8..fa5b5c972 100644 --- a/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj +++ b/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj @@ -63,7 +63,7 @@ Always - + From b2dd065b565b61b7b1871026178fe563ccff7112 Mon Sep 17 00:00:00 2001 From: William Chong Date: Thu, 18 Jan 2024 17:17:37 +0400 Subject: [PATCH 27/41] initial commit --- Directory.Build.props | 7 +- .../EpochExtensions.cs | 16 ++- src/EventStore.Client.Common/Shims/Index.cs | 131 ++++++++++++++++++ .../Shims/IsExternalInit.cs | 9 ++ src/EventStore.Client.Common/Shims/Range.cs | 79 +++++++++++ .../Shims/TaskCompletionSource.cs | 8 ++ ...tore.Client.PersistentSubscriptions.csproj | 3 + ...ntStoreProjectionManagementClient.State.cs | 22 ++- .../EventStore.Client.Streams.csproj | 6 + .../EventStoreClient.Append.cs | 18 ++- .../EventStoreClient.Read.cs | 8 +- ...serManagementClientCollectionExtensions.cs | 1 + src/EventStore.Client/ChannelFactory.cs | 11 ++ .../EventStore.Client.csproj | 9 ++ ...entStoreClientSettings.ConnectionString.cs | 55 +++++--- src/EventStore.Client/HttpFallback.cs | 4 + .../Interceptors/TypedExceptionInterceptor.cs | 17 +++ src/EventStore.Client/Position.cs | 4 +- src/EventStore.Client/StreamIdentifier.cs | 4 + src/EventStore.Client/Uuid.cs | 56 ++++++-- test/Directory.Build.props | 9 ++ ...lient.PersistentSubscriptions.Tests.csproj | 3 + .../SubscriptionToAll/get_info.cs | 15 +- .../EventStore.Client.Streams.Tests.csproj | 5 +- .../ApplicationInfo.cs | 19 ++- .../EventStore.Client.Tests.Common.csproj | 1 + .../Base/EventStoreClientFixtureBase.cs | 5 +- .../Fixtures/Base/EventStoreTestServer.cs | 27 ++-- .../Base/EventStoreTestServerCluster.cs | 21 ++- .../Base/EventStoreTestServerExternal.cs | 4 +- .../Fixtures/EventStoreTestCluster.cs | 9 +- .../Fixtures/EventStoreTestNode.cs | 11 +- .../FluentDockerServiceExtensions.cs | 46 +++++- .../FluentDocker/TestBypassService.cs | 4 +- .../FluentDocker/TestService.cs | 35 +++-- .../GlobalEnvironment.cs | 10 +- .../ConnectionStringTests.cs | 32 ++++- .../EventStore.Client.Tests.csproj | 15 +- .../GrpcServerCapabilitiesClientTests.cs | 4 +- 39 files changed, 627 insertions(+), 116 deletions(-) create mode 100644 src/EventStore.Client.Common/Shims/Index.cs create mode 100644 src/EventStore.Client.Common/Shims/IsExternalInit.cs create mode 100644 src/EventStore.Client.Common/Shims/Range.cs create mode 100644 src/EventStore.Client.Common/Shims/TaskCompletionSource.cs diff --git a/Directory.Build.props b/Directory.Build.props index eecbb8220..5c261f63f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - net6.0;net7.0;net8.0 + net48;net6.0;net7.0;net8.0 true enable enable @@ -16,4 +16,9 @@ 2.59.0 2.59.0 + + + + + diff --git a/src/EventStore.Client.Common/EpochExtensions.cs b/src/EventStore.Client.Common/EpochExtensions.cs index d62bdb44c..d8d4e113d 100644 --- a/src/EventStore.Client.Common/EpochExtensions.cs +++ b/src/EventStore.Client.Common/EpochExtensions.cs @@ -1,9 +1,21 @@ namespace EventStore.Client; static class EpochExtensions { - static readonly DateTime UnixEpoch = DateTime.UnixEpoch; + private const long TicksPerMillisecond = 10000; + private const long TicksPerSecond = TicksPerMillisecond * 1000; + private const long TicksPerMinute = TicksPerSecond * 60; + private const long TicksPerHour = TicksPerMinute * 60; + private const long TicksPerDay = TicksPerHour * 24; + private const int DaysPerYear = 365; + private const int DaysPer4Years = DaysPerYear * 4 + 1; + private const int DaysPer100Years = DaysPer4Years * 25 - 1; + private const int DaysPer400Years = DaysPer100Years * 4 + 1; + private const int DaysTo1970 = DaysPer400Years * 4 + DaysPer100Years * 3 + DaysPer4Years * 17 + DaysPerYear; + private const long UnixEpochTicks = DaysTo1970 * TicksPerDay; + + private static readonly DateTime UnixEpoch = new(UnixEpochTicks, DateTimeKind.Utc); public static DateTime FromTicksSinceEpoch(this long value) => new(UnixEpoch.Ticks + value, DateTimeKind.Utc); public static long ToTicksSinceEpoch(this DateTime value) => (value - UnixEpoch).Ticks; -} \ No newline at end of file +} diff --git a/src/EventStore.Client.Common/Shims/Index.cs b/src/EventStore.Client.Common/Shims/Index.cs new file mode 100644 index 000000000..67af4a05d --- /dev/null +++ b/src/EventStore.Client.Common/Shims/Index.cs @@ -0,0 +1,131 @@ +#if !NET +using System.Runtime.CompilerServices; + +namespace System; + +/// Represent a type can be used to index a collection either from the start or the end. +/// +/// Index is used by the C# compiler to support the new index syntax +/// +/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ; +/// int lastElement = someArray[^1]; // lastElement = 5 +/// +/// +internal readonly struct Index : IEquatable +{ + private readonly int _value; + + /// Construct an Index using a value and indicating if the index is from the start or from the end. + /// The index value. it has to be zero or positive number. + /// Indicating if the index is from the start or from the end. + /// + /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Index(int value, bool fromEnd = false) + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); + } + + if (fromEnd) + _value = ~value; + else + _value = value; + } + + // The following private constructors mainly created for perf reason to avoid the checks + private Index(int value) + { + _value = value; + } + + /// Create an Index pointing at first element. + public static Index Start => new Index(0); + + /// Create an Index pointing at beyond last element. + public static Index End => new Index(~0); + + /// Create an Index from the start at the position indicated by the value. + /// The index value from the start. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Index FromStart(int value) + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); + } + + return new Index(value); + } + + /// Create an Index from the end at the position indicated by the value. + /// The index value from the end. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Index FromEnd(int value) + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); + } + + return new Index(~value); + } + + /// Returns the index value. + public int Value + { + get + { + if (_value < 0) + return ~_value; + else + return _value; + } + } + + /// Indicates whether the index is from the start or the end. + public bool IsFromEnd => _value < 0; + + /// Calculate the offset from the start using the giving collection length. + /// The length of the collection that the Index will be used with. length has to be a positive value + /// + /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values. + /// we don't validate either the returned offset is greater than the input length. + /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and + /// then used to index a collection will get out of range exception which will be same affect as the validation. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetOffset(int length) + { + int offset = _value; + if (IsFromEnd) + { + // offset = length - (~value) + // offset = length + (~(~value) + 1) + // offset = length + value + 1 + + offset += length + 1; + } + return offset; + } + + /// Indicates whether the current Index object is equal to another object of the same type. + /// An object to compare with this object + public override bool Equals(object? value) => value is Index && _value == ((Index)value)._value; + + /// Indicates whether the current Index object is equal to another Index object. + /// An object to compare with this object + public bool Equals(Index other) => _value == other._value; + + /// Returns the hash code for this instance. + public override int GetHashCode() => _value; + + /// Converts integer number to an Index. + public static implicit operator Index(int value) => FromStart(value); + + /// Converts the value of the current Index object to its equivalent string representation. + public override string ToString() => IsFromEnd ? $"^{((uint)Value)}" : ((uint)Value).ToString(); +} +#endif diff --git a/src/EventStore.Client.Common/Shims/IsExternalInit.cs b/src/EventStore.Client.Common/Shims/IsExternalInit.cs new file mode 100644 index 000000000..a77ccc3c3 --- /dev/null +++ b/src/EventStore.Client.Common/Shims/IsExternalInit.cs @@ -0,0 +1,9 @@ +#if !NET +using System.ComponentModel; + +// ReSharper disable once CheckNamespace +namespace System.Runtime.CompilerServices; + +[EditorBrowsable(EditorBrowsableState.Never)] +internal class IsExternalInit{} +#endif diff --git a/src/EventStore.Client.Common/Shims/Range.cs b/src/EventStore.Client.Common/Shims/Range.cs new file mode 100644 index 000000000..9d4b88f2f --- /dev/null +++ b/src/EventStore.Client.Common/Shims/Range.cs @@ -0,0 +1,79 @@ +#if !NET +using System.Runtime.CompilerServices; + +namespace System; + +/// Represent a range has start and end indexes. +/// +/// Range is used by the C# compiler to support the range syntax. +/// +/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 }; +/// int[] subArray1 = someArray[0..2]; // { 1, 2 } +/// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 } +/// +/// +internal readonly struct Range : IEquatable +{ + /// Represent the inclusive start index of the Range. + public Index Start { get; } + + /// Represent the exclusive end index of the Range. + public Index End { get; } + + /// Construct a Range object using the start and end indexes. + /// Represent the inclusive start index of the range. + /// Represent the exclusive end index of the range. + public Range(Index start, Index end) + { + Start = start; + End = end; + } + + /// Indicates whether the current Range object is equal to another object of the same type. + /// An object to compare with this object + public override bool Equals(object? value) => + value is Range r && + r.Start.Equals(Start) && + r.End.Equals(End); + + /// Indicates whether the current Range object is equal to another Range object. + /// An object to compare with this object + public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End); + + /// Returns the hash code for this instance. + public override int GetHashCode() => Start.GetHashCode() * 31 + End.GetHashCode(); + + /// Converts the value of the current Range object to its equivalent string representation. + public override string ToString() => $"{Start}..{End}"; + + /// Create a Range object starting from start index to the end of the collection. + public static Range StartAt(Index start) => new(start, Index.End); + + /// Create a Range object starting from first element in the collection to the end Index. + public static Range EndAt(Index end) => new(Index.Start, end); + + /// Create a Range object starting from first element to the end. + public static Range All => new(Index.Start, Index.End); + + /// Calculate the start offset and length of range object using a collection length. + /// The length of the collection that the range will be used with. length has to be a positive value. + /// + /// For performance reason, we don't validate the input length parameter against negative values. + /// It is expected Range will be used with collections which always have non negative length/count. + /// We validate the range is inside the length scope though. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public (int Offset, int Length) GetOffsetAndLength(int length) + { + int start = Start.GetOffset(length); + int end = End.GetOffset(length); + + if ((uint)end > (uint)length || (uint)start > (uint)end) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + return (start, end - start); + } +} +#endif diff --git a/src/EventStore.Client.Common/Shims/TaskCompletionSource.cs b/src/EventStore.Client.Common/Shims/TaskCompletionSource.cs new file mode 100644 index 000000000..e7e88a97f --- /dev/null +++ b/src/EventStore.Client.Common/Shims/TaskCompletionSource.cs @@ -0,0 +1,8 @@ +#if !NET +namespace System.Threading.Tasks; + +internal class TaskCompletionSource : TaskCompletionSource { + public void SetResult() => base.SetResult(null); + public bool TrySetResult() => base.TrySetResult(null); +} +#endif diff --git a/src/EventStore.Client.PersistentSubscriptions/EventStore.Client.PersistentSubscriptions.csproj b/src/EventStore.Client.PersistentSubscriptions/EventStore.Client.PersistentSubscriptions.csproj index 855b8780f..8d6521549 100644 --- a/src/EventStore.Client.PersistentSubscriptions/EventStore.Client.PersistentSubscriptions.csproj +++ b/src/EventStore.Client.PersistentSubscriptions/EventStore.Client.PersistentSubscriptions.csproj @@ -3,4 +3,7 @@ The GRPC client API for Event Store Persistent Subscriptions. Get the open source or commercial versions of Event Store server from https://eventstore.com/ + + + diff --git a/src/EventStore.Client.ProjectionManagement/EventStoreProjectionManagementClient.State.cs b/src/EventStore.Client.ProjectionManagement/EventStoreProjectionManagementClient.State.cs index ec23295a5..64187fe1f 100644 --- a/src/EventStore.Client.ProjectionManagement/EventStoreProjectionManagementClient.State.cs +++ b/src/EventStore.Client.ProjectionManagement/EventStoreProjectionManagementClient.State.cs @@ -26,7 +26,11 @@ public async Task GetResultAsync(string name, string? partition = var value = await GetResultInternalAsync(name, partition, deadline, userCredentials, cancellationToken) .ConfigureAwait(false); +#if NET await using var stream = new MemoryStream(); +#else + using var stream = new MemoryStream(); +#endif await using var writer = new Utf8JsonWriter(stream); var serializer = new ValueSerializer(); serializer.Write(writer, value, DefaultJsonSerializerOptions); @@ -53,7 +57,11 @@ public async Task GetResultAsync(string name, string? partition = null, CancellationToken cancellationToken = default) { var value = await GetResultInternalAsync(name, partition, deadline, userCredentials, cancellationToken) .ConfigureAwait(false); +#if NET await using var stream = new MemoryStream(); +#else + using var stream = new MemoryStream(); +#endif await using var writer = new Utf8JsonWriter(stream); var serializer = new ValueSerializer(); serializer.Write(writer, value, DefaultJsonSerializerOptions); @@ -93,7 +101,11 @@ public async Task GetStateAsync(string name, string? partition = n var value = await GetStateInternalAsync(name, partition, deadline, userCredentials, cancellationToken) .ConfigureAwait(false); +#if NET await using var stream = new MemoryStream(); +#else + using var stream = new MemoryStream(); +#endif await using var writer = new Utf8JsonWriter(stream); var serializer = new ValueSerializer(); serializer.Write(writer, value, DefaultJsonSerializerOptions); @@ -120,7 +132,11 @@ public async Task GetStateAsync(string name, string? partition = null, var value = await GetStateInternalAsync(name, partition, deadline, userCredentials, cancellationToken) .ConfigureAwait(false); +#if NET await using var stream = new MemoryStream(); +#else + using var stream = new MemoryStream(); +#endif await using var writer = new Utf8JsonWriter(stream); var serializer = new ValueSerializer(); serializer.Write(writer, value, DefaultJsonSerializerOptions); @@ -175,9 +191,9 @@ public override void Write(Utf8JsonWriter writer, Value value, JsonSerializerOpt break; case Value.KindOneofCase.StructValue: writer.WriteStartObject(); - foreach (var (name, item) in value.StructValue.Fields) { - writer.WritePropertyName(name); - Write(writer, item, options); + foreach (var map in value.StructValue.Fields) { + writer.WritePropertyName(map.Key); + Write(writer, map.Value, options); } writer.WriteEndObject(); diff --git a/src/EventStore.Client.Streams/EventStore.Client.Streams.csproj b/src/EventStore.Client.Streams/EventStore.Client.Streams.csproj index 430821e42..41ccec721 100644 --- a/src/EventStore.Client.Streams/EventStore.Client.Streams.csproj +++ b/src/EventStore.Client.Streams/EventStore.Client.Streams.csproj @@ -3,4 +3,10 @@ The GRPC client API for Event Store Streams. Get the open source or commercial versions of Event Store server from https://eventstore.com/ + + + + + + diff --git a/src/EventStore.Client.Streams/EventStoreClient.Append.cs b/src/EventStore.Client.Streams/EventStoreClient.Append.cs index a780f1e00..815aa4196 100644 --- a/src/EventStore.Client.Streams/EventStoreClient.Append.cs +++ b/src/EventStore.Client.Streams/EventStoreClient.Append.cs @@ -8,6 +8,8 @@ using EventStore.Client.Streams; using Grpc.Core; using Microsoft.Extensions.Logging; +using System.Runtime.CompilerServices; + namespace EventStore.Client { public partial class EventStoreClient { /// @@ -257,8 +259,8 @@ private async Task Receive() { // complete whatever tcs's we have _onException(ex); - foreach (var (_, source) in _pendingRequests) { - source.TrySetException(ex); + foreach (var request in _pendingRequests) { + request.Value.TrySetException(ex); } } } @@ -268,7 +270,7 @@ private async Task Send() { if (call is null) throw new NotSupportedException("Server does not support batch append"); - await foreach (var appendRequest in _channel.Reader.ReadAllAsync(_cancellationToken) + await foreach (var appendRequest in ReadAllAsync(_channel.Reader, _cancellationToken) .ConfigureAwait(false)) { await call.RequestStream.WriteAsync(appendRequest).ConfigureAwait(false); } @@ -339,5 +341,15 @@ public void Dispose() { _channel.Writer.TryComplete(); } } + private static async IAsyncEnumerable ReadAllAsync(ChannelReader reader, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + while (await reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + { + while (reader.TryRead(out T? item)) + { + yield return item; + } + } + } } } diff --git a/src/EventStore.Client.Streams/EventStoreClient.Read.cs b/src/EventStore.Client.Streams/EventStoreClient.Read.cs index 25635f26a..39cc588d9 100644 --- a/src/EventStore.Client.Streams/EventStoreClient.Read.cs +++ b/src/EventStore.Client.Streams/EventStoreClient.Read.cs @@ -87,7 +87,7 @@ async IAsyncEnumerable GetMessages() { } try { - await foreach (var message in _channel.Reader.ReadAllAsync().ConfigureAwait(false)) { + await foreach (var message in ReadAllAsync(_channel.Reader).ConfigureAwait(false)) { if (message is StreamMessage.LastAllStreamPosition(var position)) { LastPosition = position; } @@ -151,7 +151,7 @@ public async IAsyncEnumerator GetAsyncEnumerator( CancellationToken cancellationToken = default) { try { - await foreach (var message in _channel.Reader.ReadAllAsync(cancellationToken).ConfigureAwait(false)) { + await foreach (var message in ReadAllAsync(_channel.Reader, cancellationToken).ConfigureAwait(false)) { if (message is not StreamMessage.Event e) { continue; } @@ -250,7 +250,7 @@ async IAsyncEnumerable GetMessages() { } try { - await foreach (var message in _channel.Reader.ReadAllAsync().ConfigureAwait(false)) { + await foreach (var message in ReadAllAsync(_channel.Reader).ConfigureAwait(false)) { switch (message) { case StreamMessage.FirstStreamPosition(var streamPosition): FirstStreamPosition = streamPosition; @@ -348,7 +348,7 @@ public async IAsyncEnumerator GetAsyncEnumerator( CancellationToken cancellationToken = default) { try { - await foreach (var message in _channel.Reader.ReadAllAsync(cancellationToken).ConfigureAwait(false)) { + await foreach (var message in ReadAllAsync(_channel.Reader, cancellationToken).ConfigureAwait(false)) { if (message is StreamMessage.NotFound) { throw new StreamNotFoundException(StreamName); } diff --git a/src/EventStore.Client.UserManagement/EventStoreUserManagementClientCollectionExtensions.cs b/src/EventStore.Client.UserManagement/EventStoreUserManagementClientCollectionExtensions.cs index 539f5849f..2b25f816c 100644 --- a/src/EventStore.Client.UserManagement/EventStoreUserManagementClientCollectionExtensions.cs +++ b/src/EventStore.Client.UserManagement/EventStoreUserManagementClientCollectionExtensions.cs @@ -1,5 +1,6 @@ // ReSharper disable CheckNamespace +using System.Net.Http; using EventStore.Client; using Grpc.Core.Interceptors; using Microsoft.Extensions.DependencyInjection.Extensions; diff --git a/src/EventStore.Client/ChannelFactory.cs b/src/EventStore.Client/ChannelFactory.cs index 06d2888de..594394469 100644 --- a/src/EventStore.Client/ChannelFactory.cs +++ b/src/EventStore.Client/ChannelFactory.cs @@ -19,7 +19,9 @@ public static TChannel CreateChannel(EventStoreClientSettings settings, EndPoint return TChannel.ForAddress(address, new GrpcChannelOptions { HttpClient = new HttpClient(CreateHandler(), true) { Timeout = System.Threading.Timeout.InfiniteTimeSpan, +#if NET DefaultRequestVersion = new Version(2, 0), +#endif }, LoggerFactory = settings.LoggerFactory, Credentials = settings.ChannelCredentials, @@ -32,11 +34,20 @@ HttpMessageHandler CreateHandler() { return settings.CreateHttpMessageHandler.Invoke(); } +#if NET return new SocketsHttpHandler { KeepAlivePingDelay = settings.ConnectivitySettings.KeepAliveInterval, KeepAlivePingTimeout = settings.ConnectivitySettings.KeepAliveTimeout, EnableMultipleHttp2Connections = true }; +#else + return new WinHttpHandler { + TcpKeepAliveEnabled = true, + TcpKeepAliveTime = settings.ConnectivitySettings.KeepAliveTimeout, + TcpKeepAliveInterval = settings.ConnectivitySettings.KeepAliveInterval, + EnableMultipleHttp2Connections = true + }; +#endif } } } diff --git a/src/EventStore.Client/EventStore.Client.csproj b/src/EventStore.Client/EventStore.Client.csproj index 92a14142f..2d2e1b40f 100644 --- a/src/EventStore.Client/EventStore.Client.csproj +++ b/src/EventStore.Client/EventStore.Client.csproj @@ -21,4 +21,13 @@ + + + + + + + + + diff --git a/src/EventStore.Client/EventStoreClientSettings.ConnectionString.cs b/src/EventStore.Client/EventStoreClientSettings.ConnectionString.cs index 76b3c9c6f..37d9da65b 100644 --- a/src/EventStore.Client/EventStoreClientSettings.ConnectionString.cs +++ b/src/EventStore.Client/EventStoreClientSettings.ConnectionString.cs @@ -105,30 +105,35 @@ public static EventStoreClientSettings Parse(string connectionString) { return CreateSettings(scheme, userInfo, hosts, options); } - private static EventStoreClientSettings CreateSettings(string scheme, (string user, string pass)? userInfo, - EndPoint[] hosts, Dictionary options) { + private static EventStoreClientSettings CreateSettings( + string scheme, (string user, string pass)? userInfo, + EndPoint[] hosts, Dictionary options + ) { var settings = new EventStoreClientSettings { ConnectivitySettings = EventStoreClientConnectivitySettings.Default, - OperationOptions = EventStoreClientOperationOptions.Default + OperationOptions = EventStoreClientOperationOptions.Default }; if (userInfo.HasValue) settings.DefaultCredentials = new UserCredentials(userInfo.Value.user, userInfo.Value.pass); var typedOptions = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - foreach (var (key, value) in options) { - if (!SettingsType.TryGetValue(key, out var type)) - throw new InvalidSettingException($"Unknown option: {key}"); + foreach (var kv in options) { + if (!SettingsType.TryGetValue(kv.Key, out var type)) + throw new InvalidSettingException($"Unknown option: {kv.Key}"); + if (type == typeof(int)) { - if (!int.TryParse(value, out var intValue)) - throw new InvalidSettingException($"{key} must be an integer value"); - typedOptions.Add(key, intValue); + if (!int.TryParse(kv.Value, out var intValue)) + throw new InvalidSettingException($"{kv.Key} must be an integer value"); + + typedOptions.Add(kv.Key, intValue); } else if (type == typeof(bool)) { - if (!bool.TryParse(value, out var boolValue)) - throw new InvalidSettingException($"{key} must be either true or false"); - typedOptions.Add(key, boolValue); + if (!bool.TryParse(kv.Value, out var boolValue)) + throw new InvalidSettingException($"{kv.Key} must be either true or false"); + + typedOptions.Add(kv.Key, boolValue); } else if (type == typeof(string)) { - typedOptions.Add(key, value); + typedOptions.Add(kv.Key, kv.Value); } } @@ -198,8 +203,13 @@ private static EventStoreClientSettings CreateSettings(string scheme, (string us if (typedOptions.TryGetValue(TlsVerifyCert, out var tlsVerifyCert)) { settings.ConnectivitySettings.TlsVerifyCert = (bool)tlsVerifyCert; } - - settings.CreateHttpMessageHandler = () => { + + settings.CreateHttpMessageHandler = CreateDefaultHandler; + + return settings; + + HttpMessageHandler CreateDefaultHandler() { +#if NET var handler = new SocketsHttpHandler { KeepAlivePingDelay = settings.ConnectivitySettings.KeepAliveInterval, KeepAlivePingTimeout = settings.ConnectivitySettings.KeepAliveTimeout, @@ -209,11 +219,20 @@ private static EventStoreClientSettings CreateSettings(string scheme, (string us if (!settings.ConnectivitySettings.TlsVerifyCert) { handler.SslOptions.RemoteCertificateValidationCallback = delegate { return true; }; } +#else + var handler = new WinHttpHandler { + TcpKeepAliveEnabled = true, + TcpKeepAliveTime = settings.ConnectivitySettings.KeepAliveTimeout, + TcpKeepAliveInterval = settings.ConnectivitySettings.KeepAliveInterval, + EnableMultipleHttp2Connections = true + }; + if (!settings.ConnectivitySettings.TlsVerifyCert) { + handler.ServerCertificateValidationCallback = delegate { return true; }; + } +#endif return handler; - }; - - return settings; + } } private static string ParseScheme(string s) => diff --git a/src/EventStore.Client/HttpFallback.cs b/src/EventStore.Client/HttpFallback.cs index 2a734a1c0..c1b213591 100644 --- a/src/EventStore.Client/HttpFallback.cs +++ b/src/EventStore.Client/HttpFallback.cs @@ -39,7 +39,11 @@ internal async Task HttpGetAsync(string path, ChannelInfo channelInfo, Tim var httpResult = await HttpSendAsync(request, onNotFound, deadline, cancellationToken).ConfigureAwait(false); +#if NET var json = await httpResult.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); +#else + var json = await httpResult.Content.ReadAsStringAsync().ConfigureAwait(false); +#endif var result = JsonSerializer.Deserialize(json, _jsonSettings); if (result == null) { diff --git a/src/EventStore.Client/Interceptors/TypedExceptionInterceptor.cs b/src/EventStore.Client/Interceptors/TypedExceptionInterceptor.cs index 75754f95f..0781eb9ef 100644 --- a/src/EventStore.Client/Interceptors/TypedExceptionInterceptor.cs +++ b/src/EventStore.Client/Interceptors/TypedExceptionInterceptor.cs @@ -13,8 +13,13 @@ class TypedExceptionInterceptor : Interceptor { [Exceptions.NotLeader] = ex => ex.ToNotLeaderException(), }; +#if NET48 + public TypedExceptionInterceptor(Dictionary> customExceptionMap) { + var map = new Dictionary>(DefaultExceptionMap); +#else public TypedExceptionInterceptor(Dictionary> customExceptionMap) { var map = new Dictionary>(DefaultExceptionMap.Concat(customExceptionMap)); +#endif ConvertRpcException = rpcEx => { if (rpcEx.TryMapException(map, out var ex)) @@ -117,6 +122,17 @@ public static NotAuthenticatedException ToNotAuthenticatedException(this RpcExce public static RpcException ToDeadlineExceededRpcException(this RpcException exception) => new(new Status(DeadlineExceeded, exception.Status.Detail, exception.Status.DebugException)); +#if NET48 + public static bool TryMapException(this RpcException exception, Dictionary> map, out Exception createdException) { + if (exception.Trailers.TryGetValue(Exceptions.ExceptionKey, out var key) && map.TryGetValue(key!, out var factory)) { + createdException = factory.Invoke(exception); + return true; + } + + createdException = null!; + return false; + } +#else public static bool TryMapException(this RpcException exception, Dictionary> map, [MaybeNullWhen(false)] out Exception createdException) { if (exception.Trailers.TryGetValue(Exceptions.ExceptionKey, out var key) && map.TryGetValue(key!, out var factory)) { createdException = factory.Invoke(exception); @@ -126,6 +142,7 @@ public static bool TryMapException(this RpcException exception, Dictionary(IAsyncStreamReader reader, Func convertException) : IAsyncStreamReader { diff --git a/src/EventStore.Client/Position.cs b/src/EventStore.Client/Position.cs index 71fb54ef8..169439804 100644 --- a/src/EventStore.Client/Position.cs +++ b/src/EventStore.Client/Position.cs @@ -164,7 +164,7 @@ public bool Equals(Position other) => /// true if the value was converted successfully; otherwise, false. public static bool TryParse(string value, out Position? position) { position = null; - var parts = value.Split("/"); + var parts = value.Split('/'); if (parts.Length != 2) { return false; @@ -184,7 +184,7 @@ public static bool TryParse(string value, out Position? position) { static bool TryParsePosition(string expectedPrefix, string v, out ulong p) { p = 0; - var prts = v.Split(":"); + var prts = v.Split(':'); if (prts.Length != 2) { return false; } diff --git a/src/EventStore.Client/StreamIdentifier.cs b/src/EventStore.Client/StreamIdentifier.cs index fd0ed8d4f..73e0ee25e 100644 --- a/src/EventStore.Client/StreamIdentifier.cs +++ b/src/EventStore.Client/StreamIdentifier.cs @@ -12,7 +12,11 @@ public partial class StreamIdentifier { } if (source._cached != null || source.StreamName.IsEmpty) return source._cached; +#if NET var tmp = Encoding.UTF8.GetString(source.StreamName.Span); +#else + var tmp = Encoding.UTF8.GetString(source.StreamName.ToByteArray()); +#endif //this doesn't have to be thread safe, its just a cache in case the identifier is turned into a string several times source._cached = tmp; return source._cached; diff --git a/src/EventStore.Client/Uuid.cs b/src/EventStore.Client/Uuid.cs index 98b6f9ff6..8f5d2ef45 100644 --- a/src/EventStore.Client/Uuid.cs +++ b/src/EventStore.Client/Uuid.cs @@ -1,4 +1,6 @@ using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace EventStore.Client { /// @@ -66,20 +68,24 @@ private Uuid(Guid value) { Span data = stackalloc byte[16]; - if (!value.TryWriteBytes(data)) { + if (!TryWriteGuidBytes(value, data)) { throw new InvalidOperationException(); } - data.Slice(0, 8).Reverse(); - data.Slice(0, 2).Reverse(); + data[..8].Reverse(); + data[..2].Reverse(); data.Slice(2, 2).Reverse(); data.Slice(4, 4).Reverse(); - data.Slice(8).Reverse(); + data[8..].Reverse(); - _msb = BitConverter.ToInt64(data); - _lsb = BitConverter.ToInt64(data.Slice(8)); + _msb = BitConverterToInt64(data); + _lsb = BitConverterToInt64(data[8..]); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static long BitConverterToInt64(ReadOnlySpan value) + => Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(value)); + private Uuid(string value) : this(value == null ? throw new ArgumentNullException(nameof(value)) : Guid.Parse(value)) { @@ -98,7 +104,7 @@ public UUID ToDto() => new UUID { Structured = new UUID.Types.Structured { LeastSignificantBits = _lsb, - MostSignificantBits = _msb + MostSignificantBits = _msb } }; @@ -148,18 +154,44 @@ public Guid ToGuid() { } Span data = stackalloc byte[16]; - if (!BitConverter.TryWriteBytes(data, _msb) || - !BitConverter.TryWriteBytes(data.Slice(8), _lsb)) { + if (!TryWriteBytes(data, _msb) || + !TryWriteBytes(data[8..], _lsb)) { throw new InvalidOperationException(); } - data.Slice(0, 8).Reverse(); - data.Slice(0, 4).Reverse(); + data[..8].Reverse(); + data[..4].Reverse(); data.Slice(4, 2).Reverse(); data.Slice(6, 2).Reverse(); - data.Slice(8).Reverse(); + data[8..].Reverse(); +#if NET return new Guid(data); +#else + return new Guid(data.ToArray()); +#endif + } + private static bool TryWriteBytes(Span destination, long value) + { + if (destination.Length < sizeof(long)) + return false; + + Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), value); + return true; + } + + private bool TryWriteGuidBytes(Guid value, Span destination) + { +#if NET + return value.TryWriteBytes(destination); +#else + if (destination.Length < 16) + return false; + + var bytes = value.ToByteArray(); + bytes.CopyTo(destination); + return true; +#endif } } } diff --git a/test/Directory.Build.props b/test/Directory.Build.props index a4de0b3a7..7fb82e26d 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -29,4 +29,13 @@ + + + + + + + + + diff --git a/test/EventStore.Client.PersistentSubscriptions.Tests/EventStore.Client.PersistentSubscriptions.Tests.csproj b/test/EventStore.Client.PersistentSubscriptions.Tests/EventStore.Client.PersistentSubscriptions.Tests.csproj index cd15c30d9..a9517973c 100644 --- a/test/EventStore.Client.PersistentSubscriptions.Tests/EventStore.Client.PersistentSubscriptions.Tests.csproj +++ b/test/EventStore.Client.PersistentSubscriptions.Tests/EventStore.Client.PersistentSubscriptions.Tests.csproj @@ -6,4 +6,7 @@ + + + diff --git a/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/get_info.cs b/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/get_info.cs index ce1b19108..ca3afffd6 100644 --- a/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/get_info.cs +++ b/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/get_info.cs @@ -164,12 +164,13 @@ await Client.SubscribeToAllAsync( (s, e, r, ct) => { counter++; - if (counter == 1) - s.Nack(PersistentSubscriptionNakEventAction.Park, "Test", e); - - if (counter > 10) - tcs.TrySetResult(); - + switch (counter) { + case 1: s.Nack(PersistentSubscriptionNakEventAction.Park, "Test", e); + break; + case > 10: + tcs.TrySetResult(); + break; + } return Task.CompletedTask; }, userCredentials: TestCredentials.Root @@ -178,4 +179,4 @@ await Client.SubscribeToAllAsync( await tcs.Task; } } -} \ No newline at end of file +} diff --git a/test/EventStore.Client.Streams.Tests/EventStore.Client.Streams.Tests.csproj b/test/EventStore.Client.Streams.Tests/EventStore.Client.Streams.Tests.csproj index d32b56a43..18e956804 100644 --- a/test/EventStore.Client.Streams.Tests/EventStore.Client.Streams.Tests.csproj +++ b/test/EventStore.Client.Streams.Tests/EventStore.Client.Streams.Tests.csproj @@ -6,4 +6,7 @@ - \ No newline at end of file + + + + diff --git a/test/EventStore.Client.Tests.Common/ApplicationInfo.cs b/test/EventStore.Client.Tests.Common/ApplicationInfo.cs index 8a7a1ec7a..d9e440cb0 100644 --- a/test/EventStore.Client.Tests.Common/ApplicationInfo.cs +++ b/test/EventStore.Client.Tests.Common/ApplicationInfo.cs @@ -23,17 +23,22 @@ static Application() { Environment = GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? Environments.Development; + // var builder = new ConfigurationBuilder() + // .AddJsonFile("appsettings.json", true) + // .AddJsonFile($"appsettings.{Environment}.json", true) // Accept default naming convention + // .AddJsonFile($"appsettings.{Environment.ToLowerInvariant()}.json", true) // Linux is case sensitive + // .AddEnvironmentVariables(); + var builder = new ConfigurationBuilder() .AddJsonFile("appsettings.json", true) - .AddJsonFile($"appsettings.{Environment}.json", true) // Accept default naming convention - .AddJsonFile($"appsettings.{Environment.ToLowerInvariant()}.json", true) // Linux is case sensitive - .AddEnvironmentVariables(); + .AddJsonFile($"appsettings.{Environment}.json", true) // Accept default naming convention + .AddJsonFile($"appsettings.{Environment.ToLowerInvariant()}.json", true); // Linux is case sensitive Configuration = builder.Build(); - WriteLine($"APP: {Environment} configuration loaded " - + $"with {Configuration.AsEnumerable().Count()} entries " - + $"from {builder.Sources.Count} sources."); + // WriteLine($"APP: {Environment} configuration loaded " + // + $"with {builder.AsEnumerable().Count()} entries " + // + $"from {builder} sources."); IsDevelopment = IsEnvironment(Environments.Development); IsStaging = IsEnvironment(Environments.Staging); @@ -65,4 +70,4 @@ public static class OperatingSystem { public static bool IsMacOS() => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); public static bool IsLinux() => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); } -} \ No newline at end of file +} diff --git a/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj b/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj index ba1a806f8..b5015ef4e 100644 --- a/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj +++ b/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj @@ -26,6 +26,7 @@ + diff --git a/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreClientFixtureBase.cs b/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreClientFixtureBase.cs index 2a5076437..ba93a7ad5 100644 --- a/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreClientFixtureBase.cs +++ b/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreClientFixtureBase.cs @@ -88,9 +88,6 @@ static void ConfigureLogging() { .WriteTo.Seq("http://localhost:5341/", period: TimeSpan.FromMilliseconds(1)); Log.Logger = loggerConfiguration.CreateLogger(); -#if GRPC_CORE - GrpcEnvironment.SetLogger(new GrpcCoreSerilogLogger(Log.Logger.ForContext())); -#endif AppDomain.CurrentDomain.DomainUnload += (_, e) => Log.CloseAndFlush(); } @@ -147,4 +144,4 @@ public void CaptureLogs(ITestOutputHelper testOutputHelper) { _disposables.Add(subscription); } -} \ No newline at end of file +} diff --git a/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServer.cs b/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServer.cs index 772946347..fb7e5f601 100644 --- a/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServer.cs +++ b/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServer.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Net.Http; using Ductus.FluentDocker.Builders; using Ductus.FluentDocker.Extensions; using Ductus.FluentDocker.Model.Builders; @@ -24,13 +25,19 @@ public EventStoreTestServer( _hostCertificatePath = hostCertificatePath; VerifyCertificatesExist(); - _httpClient = new( - new SocketsHttpHandler { - SslOptions = { RemoteCertificateValidationCallback = delegate { return true; } } - } - ) { - BaseAddress = address +#if NET + _httpClient = new HttpClient(new SocketsHttpHandler { + SslOptions = {RemoteCertificateValidationCallback = delegate { return true; }} + }) { + BaseAddress = address, }; +#else + _httpClient = new HttpClient(new WinHttpHandler { + ServerCertificateValidationCallback = delegate { return true; } + }) { + BaseAddress = address, + }; +#endif var env = new Dictionary { ["EVENTSTORE_DB_LOG_FORMAT"] = "V2", @@ -46,8 +53,8 @@ public EventStoreTestServer( ["EVENTSTORE_DISABLE_LOG_FILE"] = "true" }; - foreach (var (key, value) in envOverrides ?? Enumerable.Empty>()) - env[key] = value; + foreach (var val in envOverrides ?? Enumerable.Empty>()) + env[val.Key] = val.Value; _eventStore = new Builder() .UseContainer() @@ -89,7 +96,7 @@ public ValueTask DisposeAsync() { _httpClient?.Dispose(); _eventStore?.Dispose(); - return ValueTask.CompletedTask; + return new ValueTask(Task.CompletedTask); } static Version GetVersion() { @@ -125,4 +132,4 @@ void VerifyCertificatesExist() { $"Could not locate the certificates file {file} needed to run EventStoreDB. Please run the 'gencert' tool at the root of the repository." ); } -} \ No newline at end of file +} diff --git a/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServerCluster.cs b/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServerCluster.cs index e9317c88e..ceb263e15 100644 --- a/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServerCluster.cs +++ b/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServerCluster.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Net.Http; using Ductus.FluentDocker.Builders; using Ductus.FluentDocker.Common; using Ductus.FluentDocker.Services; @@ -21,13 +22,19 @@ public EventStoreTestServerCluster( _eventStoreCluster = BuildCluster(envOverrides); - _httpClient = new( - new SocketsHttpHandler { - SslOptions = { RemoteCertificateValidationCallback = delegate { return true; } } - } - ) { - BaseAddress = address +#if NET + _httpClient = new HttpClient(new SocketsHttpHandler { + SslOptions = {RemoteCertificateValidationCallback = delegate { return true; }} + }) { + BaseAddress = address, }; +#else + _httpClient = new HttpClient(new WinHttpHandler { + ServerCertificateValidationCallback = delegate { return true; } + }) { + BaseAddress = address, + }; +#endif } public async Task StartAsync(CancellationToken cancellationToken = default) { @@ -83,4 +90,4 @@ ICompositeService BuildCluster(IDictionary? envOverrides = null) .RemoveOrphans() .Build(); } -} \ No newline at end of file +} diff --git a/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServerExternal.cs b/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServerExternal.cs index 1b6ff3492..19b866a63 100644 --- a/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServerExternal.cs +++ b/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServerExternal.cs @@ -4,5 +4,5 @@ public class EventStoreTestServerExternal : IEventStoreTestServer { public Task StartAsync(CancellationToken cancellationToken = default) => Task.CompletedTask; public void Stop() { } - public ValueTask DisposeAsync() => ValueTask.CompletedTask; -} \ No newline at end of file + public ValueTask DisposeAsync() => new ValueTask(Task.CompletedTask); +} diff --git a/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestCluster.cs b/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestCluster.cs index f97b852e2..682995ae6 100644 --- a/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestCluster.cs +++ b/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestCluster.cs @@ -48,6 +48,11 @@ protected override CompositeBuilder Configure() { } protected override async Task OnServiceStarted() { - await Service.WaitUntilNodesAreHealthy("esdb-node", TimeSpan.FromSeconds(60)); +#if NET48 + Service.WaitUntilNodesAreHealthy("esdb-node", TimeSpan.FromSeconds(60)); +#else + await Service.WaitUntilNodesAreHealthy("esdb-node", TimeSpan.FromSeconds(60)); +#endif + await Task.CompletedTask; } -} \ No newline at end of file +} diff --git a/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestNode.cs b/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestNode.cs index 46ae316da..97dc3725e 100644 --- a/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestNode.cs +++ b/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestNode.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Net.Http; using System.Net.Sockets; using Ductus.FluentDocker.Builders; using Ductus.FluentDocker.Common; @@ -82,7 +83,11 @@ protected override ContainerBuilder Configure() { protected override async Task OnServiceStarted() { using var http = new HttpClient( +#if NET new SocketsHttpHandler { SslOptions = { RemoteCertificateValidationCallback = delegate { return true; } } } +#else + new WinHttpHandler { ServerCertificateValidationCallback = delegate { return true; } } +#endif ) { BaseAddress = Options.ClientSettings.ConnectivitySettings.Address }; @@ -132,7 +137,11 @@ public async Task GetNextAvailablePort(TimeSpan delay = default) { await Task.Delay(delay); } finally { +#if NET if (socket.Connected) await socket.DisconnectAsync(true); +#else + if (socket.Connected) socket.Disconnect(true); +#endif } } } @@ -142,4 +151,4 @@ public async Task GetNextAvailablePort(TimeSpan delay = default) { } public int NextAvailablePort => GetNextAvailablePort(FromMilliseconds(100)).GetAwaiter().GetResult(); -} \ No newline at end of file +} diff --git a/test/EventStore.Client.Tests.Common/FluentDocker/FluentDockerServiceExtensions.cs b/test/EventStore.Client.Tests.Common/FluentDocker/FluentDockerServiceExtensions.cs index e763edd35..2e2f74fc3 100644 --- a/test/EventStore.Client.Tests.Common/FluentDocker/FluentDockerServiceExtensions.cs +++ b/test/EventStore.Client.Tests.Common/FluentDocker/FluentDockerServiceExtensions.cs @@ -27,19 +27,55 @@ public static async ValueTask WaitUntilNodesAreHealthy(this IContainerService se await WaitUntilNodesAreHealthy(service, cts.Token); } - public static async Task WaitUntilNodesAreHealthy(this ICompositeService service, IEnumerable services, CancellationToken cancellationToken) { +#if NET + public static async Task WaitUntilNodesAreHealthy(this ICompositeService service, IEnumerable services, CancellationToken cancellationToken) +#else + public static void WaitUntilNodesAreHealthy(this ICompositeService service, IEnumerable services, CancellationToken cancellationToken) +#endif +{ var nodes = service.Containers.Where(x => services.Contains(x.Name)); - + +#if NET await Parallel.ForEachAsync(nodes, cancellationToken, async (node, ct) => await node.WaitUntilNodesAreHealthy(ct)); - } +#else + Parallel.ForEach( + nodes, + node => { + node.WaitUntilNodesAreHealthy(cancellationToken).GetAwaiter().GetResult(); + } + ); +#endif + } - public static async Task WaitUntilNodesAreHealthy(this ICompositeService service, string serviceNamePrefix, CancellationToken cancellationToken) { +#if NET + public static async Task WaitUntilNodesAreHealthy(this ICompositeService service, string serviceNamePrefix, CancellationToken cancellationToken) +#else + public static void WaitUntilNodesAreHealthy(this ICompositeService service, string serviceNamePrefix, CancellationToken cancellationToken) +#endif +{ var nodes = service.Containers.Where(x => x.Name.StartsWith(serviceNamePrefix)); +#if NET await Parallel.ForEachAsync(nodes, cancellationToken, async (node, ct) => await node.WaitUntilNodesAreHealthy(ct)); +#else + Parallel.ForEach( + nodes, + node => { + node.WaitUntilNodesAreHealthy(cancellationToken).GetAwaiter().GetResult(); + } + ); +#endif } +#if NET public static async Task WaitUntilNodesAreHealthy(this ICompositeService service, string serviceNamePrefix, TimeSpan timeout) { +#else + public static void WaitUntilNodesAreHealthy(this ICompositeService service, string serviceNamePrefix, TimeSpan timeout) { +#endif using var cts = new CancellationTokenSource(timeout); +#if NET await WaitUntilNodesAreHealthy(service, serviceNamePrefix, cts.Token); +#else + WaitUntilNodesAreHealthy(service, serviceNamePrefix, cts.Token); +#endif } -} \ No newline at end of file +} diff --git a/test/EventStore.Client.Tests.Common/FluentDocker/TestBypassService.cs b/test/EventStore.Client.Tests.Common/FluentDocker/TestBypassService.cs index 2fb3e805c..3505eb9af 100644 --- a/test/EventStore.Client.Tests.Common/FluentDocker/TestBypassService.cs +++ b/test/EventStore.Client.Tests.Common/FluentDocker/TestBypassService.cs @@ -25,7 +25,7 @@ public override async Task Stop() { } } - public override ValueTask DisposeAsync() => ValueTask.CompletedTask; + public override ValueTask DisposeAsync() => new ValueTask(); } public sealed class BypassService : IService { @@ -58,4 +58,4 @@ public BypassBuilder() : this(null) { } public override BypassService Build() => new BypassService(); protected override IBuilder InternalCreate() => this; -} \ No newline at end of file +} diff --git a/test/EventStore.Client.Tests.Common/FluentDocker/TestService.cs b/test/EventStore.Client.Tests.Common/FluentDocker/TestService.cs index 026e0a12c..7ed8c3f27 100644 --- a/test/EventStore.Client.Tests.Common/FluentDocker/TestService.cs +++ b/test/EventStore.Client.Tests.Common/FluentDocker/TestService.cs @@ -1,6 +1,7 @@ using Ductus.FluentDocker.Builders; using Ductus.FluentDocker.Common; using Ductus.FluentDocker.Services; +using Google.Protobuf.WellKnownTypes; using Serilog; using static Serilog.Core.Constants; @@ -10,10 +11,14 @@ public interface ITestService : IAsyncDisposable { Task Start(); Task Stop(); Task Restart(TimeSpan delay); - - Task Restart() => Restart(TimeSpan.Zero); - - void ReportStatus(); + +#if NET + Task Restart() => Restart(TimeSpan.Zero); +#else + Task Restart(); +#endif + + void ReportStatus(); } public abstract class TestService : ITestService where TService : IService where TBuilder : BaseBuilder { @@ -25,6 +30,13 @@ public abstract class TestService : ITestService where TServ INetworkService? Network { get; set; } = null!; +#if !NET + Task ITestService.Restart() + { + return Restart(TimeSpan.Zero); + } +#endif + public virtual async Task Start() { Logger.Information("Container service starting"); @@ -63,7 +75,7 @@ public virtual async Task Stop() { throw new FluentDockerException("Failed to stop container service", ex); } } - + public virtual async Task Restart(TimeSpan delay) { try { try { @@ -73,9 +85,9 @@ public virtual async Task Restart(TimeSpan delay) { catch (Exception ex) { throw new FluentDockerException("Failed to stop container service", ex); } - + await Task.Delay(delay); - + Logger.Information("Container service starting..."); try { @@ -117,6 +129,12 @@ void ReportContainerStatus(IContainerService service) { } } +#if !NET + public virtual ValueTask DisposeAsync() + { + return DisposeAsync(); + } +#else public virtual ValueTask DisposeAsync() { try { Network?.Dispose(); @@ -134,9 +152,10 @@ public virtual ValueTask DisposeAsync() { return ValueTask.CompletedTask; } +#endif protected abstract TBuilder Configure(); protected virtual Task OnServiceStarted() => Task.CompletedTask; protected virtual Task OnServiceStop() => Task.CompletedTask; -} \ No newline at end of file +} diff --git a/test/EventStore.Client.Tests.Common/GlobalEnvironment.cs b/test/EventStore.Client.Tests.Common/GlobalEnvironment.cs index f2e1e69eb..74f8838da 100644 --- a/test/EventStore.Client.Tests.Common/GlobalEnvironment.cs +++ b/test/EventStore.Client.Tests.Common/GlobalEnvironment.cs @@ -52,11 +52,11 @@ public static IDictionary GetEnvironmentVariables(IDictionary>()) { - if (key.StartsWith("EVENTSTORE") && !SharedEnv.Contains(key)) - throw new Exception($"Add {key} to shared.env and _sharedEnv to pass it to the cluster containers"); + foreach (var @override in overrides ?? Enumerable.Empty>()) { + if (@override.Key.StartsWith("EVENTSTORE") && !SharedEnv.Contains(@override.Key)) + throw new Exception($"Add {@override.Key} to shared.env and _sharedEnv to pass it to the cluster containers"); - env[key] = value; + env[@override.Key] = @override.Value; } return env; @@ -73,4 +73,4 @@ public static IDictionary GetEnvironmentVariables(IDictionary(handler); if (!tlsVerifyCert) { Assert.NotNull(socketsHandler.SslOptions.RemoteCertificateValidationCallback); @@ -128,6 +130,16 @@ public void tls_verify_cert(bool tlsVerifyCert) { else { Assert.Null(socketsHandler.SslOptions.RemoteCertificateValidationCallback); } +#else + var socketsHandler = Assert.IsType(handler); + if (!tlsVerifyCert) { + Assert.NotNull(socketsHandler.ServerCertificateValidationCallback); + Assert.True(socketsHandler.ServerCertificateValidationCallback!.Invoke(null!, default!, + default!, default)); + } else { + Assert.Null(socketsHandler.ServerCertificateValidationCallback); + } +#endif } #endif @@ -141,10 +153,15 @@ public void infinite_grpc_timeouts() { using var handler = result.CreateHttpMessageHandler?.Invoke(); +#if NET var socketsHandler = Assert.IsType(handler); - Assert.Equal(System.Threading.Timeout.InfiniteTimeSpan, socketsHandler.KeepAlivePingTimeout); Assert.Equal(System.Threading.Timeout.InfiniteTimeSpan, socketsHandler.KeepAlivePingDelay); +#else + var winHttpHandler = Assert.IsType(handler); + Assert.Equal(System.Threading.Timeout.InfiniteTimeSpan, winHttpHandler.TcpKeepAliveTime); + Assert.Equal(System.Threading.Timeout.InfiniteTimeSpan, winHttpHandler.TcpKeepAliveInterval); +#endif } [Fact] @@ -360,13 +377,19 @@ static string GetKeyValuePairs( settings.DefaultDeadline.Value.TotalMilliseconds.ToString() ); -#if !GRPC_CORE if (settings.CreateHttpMessageHandler != null) { using var handler = settings.CreateHttpMessageHandler.Invoke(); +#if NET if (handler is SocketsHttpHandler socketsHttpHandler && socketsHttpHandler.SslOptions.RemoteCertificateValidationCallback != null) pairs.Add("tlsVerifyCert", "false"); } +#else + if (handler is WinHttpHandler winHttpHandler && + winHttpHandler.ServerCertificateValidationCallback != null) { + pairs.Add("tlsVerifyCert", "false"); + } + } #endif return string.Join("&", pairs.Select(pair => $"{getKey?.Invoke(pair.Key) ?? pair.Key}={pair.Value}")); @@ -467,6 +490,7 @@ public bool Equals(EventStoreClientOperationOptions? x, EventStoreClientOperatio return x.GetType() == y.GetType(); } - public int GetHashCode(EventStoreClientOperationOptions obj) => System.HashCode.Combine(obj.ThrowOnAppendFailure); + public int GetHashCode(EventStoreClientOperationOptions obj) => + System.HashCode.Combine(obj.ThrowOnAppendFailure); } -} \ No newline at end of file +} diff --git a/test/EventStore.Client.Tests/EventStore.Client.Tests.csproj b/test/EventStore.Client.Tests/EventStore.Client.Tests.csproj index 0bd9cef57..e6c606150 100644 --- a/test/EventStore.Client.Tests/EventStore.Client.Tests.csproj +++ b/test/EventStore.Client.Tests/EventStore.Client.Tests.csproj @@ -15,13 +15,18 @@ + + + + + + + all + runtime; build; native; contentfiles; analyzers + + - - - all - runtime; build; native; contentfiles; analyzers - diff --git a/test/EventStore.Client.Tests/GrpcServerCapabilitiesClientTests.cs b/test/EventStore.Client.Tests/GrpcServerCapabilitiesClientTests.cs index 9173f83b5..2b13cf13b 100644 --- a/test/EventStore.Client.Tests/GrpcServerCapabilitiesClientTests.cs +++ b/test/EventStore.Client.Tests/GrpcServerCapabilitiesClientTests.cs @@ -1,3 +1,4 @@ +#if NET using System.Net; using EventStore.Client.ServerFeatures; using Grpc.Core; @@ -97,4 +98,5 @@ class FakeServerFeatures : ServerFeatures.ServerFeatures.ServerFeaturesBase { public override Task GetSupportedMethods(Empty request, ServerCallContext context) => Task.FromResult(_supportedMethods); } -} \ No newline at end of file +} +#endif From c6db004fc2a872854dd127dc133c450cdfd4dd51 Mon Sep 17 00:00:00 2001 From: William Chong Date: Thu, 18 Jan 2024 17:41:44 +0400 Subject: [PATCH 28/41] Remove redudant shims --- test/Directory.Build.props | 1 - .../EventStore.Client.PersistentSubscriptions.Tests.csproj | 3 --- 2 files changed, 4 deletions(-) diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 7fb82e26d..f29b71ca0 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -35,7 +35,6 @@ - diff --git a/test/EventStore.Client.PersistentSubscriptions.Tests/EventStore.Client.PersistentSubscriptions.Tests.csproj b/test/EventStore.Client.PersistentSubscriptions.Tests/EventStore.Client.PersistentSubscriptions.Tests.csproj index a9517973c..cd15c30d9 100644 --- a/test/EventStore.Client.PersistentSubscriptions.Tests/EventStore.Client.PersistentSubscriptions.Tests.csproj +++ b/test/EventStore.Client.PersistentSubscriptions.Tests/EventStore.Client.PersistentSubscriptions.Tests.csproj @@ -6,7 +6,4 @@ - - - From 9c8b6cef7a2e85fafea4a6d4abdf772cf63d8d9d Mon Sep 17 00:00:00 2001 From: William Chong Date: Thu, 18 Jan 2024 17:49:14 +0400 Subject: [PATCH 29/41] Sauces --- .../ApplicationInfo.cs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/test/EventStore.Client.Tests.Common/ApplicationInfo.cs b/test/EventStore.Client.Tests.Common/ApplicationInfo.cs index d9e440cb0..b560d4dc8 100644 --- a/test/EventStore.Client.Tests.Common/ApplicationInfo.cs +++ b/test/EventStore.Client.Tests.Common/ApplicationInfo.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Runtime.InteropServices; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration.Json; using Microsoft.Extensions.Hosting; using static System.Console; @@ -23,12 +24,6 @@ static Application() { Environment = GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? Environments.Development; - // var builder = new ConfigurationBuilder() - // .AddJsonFile("appsettings.json", true) - // .AddJsonFile($"appsettings.{Environment}.json", true) // Accept default naming convention - // .AddJsonFile($"appsettings.{Environment.ToLowerInvariant()}.json", true) // Linux is case sensitive - // .AddEnvironmentVariables(); - var builder = new ConfigurationBuilder() .AddJsonFile("appsettings.json", true) .AddJsonFile($"appsettings.{Environment}.json", true) // Accept default naming convention @@ -36,9 +31,9 @@ static Application() { Configuration = builder.Build(); - // WriteLine($"APP: {Environment} configuration loaded " - // + $"with {builder.AsEnumerable().Count()} entries " - // + $"from {builder} sources."); + WriteLine($"APP: {Environment} configuration loaded " + + $"with {Configuration.AsEnumerable().Count()} entries " + + $"from {builder.Sources.Count()} sources."); IsDevelopment = IsEnvironment(Environments.Development); IsStaging = IsEnvironment(Environments.Staging); From 64610d00dc06ed59b4f222be4e3eea05d13d63cc Mon Sep 17 00:00:00 2001 From: William Chong Date: Thu, 18 Jan 2024 17:54:27 +0400 Subject: [PATCH 30/41] add Microsoft.Extensions.Configuration.EnvironmentVariables --- test/EventStore.Client.Tests.Common/ApplicationInfo.cs | 4 ++-- .../EventStore.Client.Tests.Common.csproj | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/test/EventStore.Client.Tests.Common/ApplicationInfo.cs b/test/EventStore.Client.Tests.Common/ApplicationInfo.cs index b560d4dc8..0120c21b4 100644 --- a/test/EventStore.Client.Tests.Common/ApplicationInfo.cs +++ b/test/EventStore.Client.Tests.Common/ApplicationInfo.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using System.Runtime.InteropServices; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Configuration.Json; using Microsoft.Extensions.Hosting; using static System.Console; @@ -27,7 +26,8 @@ static Application() { var builder = new ConfigurationBuilder() .AddJsonFile("appsettings.json", true) .AddJsonFile($"appsettings.{Environment}.json", true) // Accept default naming convention - .AddJsonFile($"appsettings.{Environment.ToLowerInvariant()}.json", true); // Linux is case sensitive + .AddJsonFile($"appsettings.{Environment.ToLowerInvariant()}.json", true) // Linux is case sensitive + .AddEnvironmentVariables(); Configuration = builder.Build(); diff --git a/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj b/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj index b5015ef4e..6c2f73fa1 100644 --- a/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj +++ b/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj @@ -27,6 +27,7 @@ + From 85efe11370d2b216522d47a6c4ef3907e5ff2a17 Mon Sep 17 00:00:00 2001 From: William Chong Date: Thu, 18 Jan 2024 18:10:04 +0400 Subject: [PATCH 31/41] Fixes --- .editorconfig | 30 +++++++------------ .github/workflows/base.yml | 10 +++---- .github/workflows/publish.yml | 27 +++++++++-------- samples/Directory.Build.props | 2 +- src/Directory.Build.props | 12 ++++++-- .../EpochExtensions.cs | 4 +-- .../EventStoreClient.Append.cs | 1 - .../EventStore.Client.csproj | 5 ++-- src/EventStore.Client/HashCode.cs | 11 +------ src/EventStore.Client/Uuid.cs | 23 -------------- test/Directory.Build.props | 7 +++-- ...lient.PersistentSubscriptions.Tests.csproj | 3 -- .../SubscriptionToStream/get_info.cs | 16 +++++----- .../Read/EventBinaryData.cs | 6 +--- .../EventStore.Client.Tests.Common.csproj | 2 +- .../ConnectionStringTests.cs | 1 - .../EventStore.Client.Tests.csproj | 10 ------- 17 files changed, 58 insertions(+), 112 deletions(-) diff --git a/.editorconfig b/.editorconfig index 56c3583ce..1a4fb776a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,8 +1,8 @@ -root = true # EditorConfig is awesome: http://EditorConfig.org # top-most EditorConfig file +root = true [*] insert_final_newline = true @@ -44,33 +44,33 @@ dotnet_style_predefined_type_for_member_access = true:suggestion # name all constant fields using PascalCase dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion -dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields -dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style -dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.applicable_kinds = field dotnet_naming_symbols.constant_fields.required_modifiers = const dotnet_naming_style.pascal_case_style.capitalization = pascal_case # static fields should have s_ prefix dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion -dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields +dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields -dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.applicable_kinds = field dotnet_naming_symbols.static_fields.required_modifiers = static -dotnet_naming_style.static_prefix_style.capitalization = pascal_case +dotnet_naming_style.static_prefix_style.capitalization = pascal_case # internal and private fields should be _camelCase dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion -dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields -dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style +dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields +dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style dotnet_naming_symbols.private_internal_fields.applicable_kinds = field dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal dotnet_naming_style.camel_case_underscore_style.required_prefix = _ -dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case +dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case # Code style defaults dotnet_sort_system_directives_first = true @@ -124,16 +124,6 @@ csharp_space_between_method_declaration_name_and_open_parenthesis = false csharp_space_between_method_declaration_parameter_list_parentheses = false csharp_space_between_parentheses = false csharp_space_between_square_brackets = false -indent_style = space -indent_size = 4 -tab_width = 4 - -# ReSharper properties -resharper_csharp_indent_style = space -resharper_default_private_modifier = explicit -resharper_default_internal_modifier = explicit -resharper_int_align_binary_expressions = false -resharper_int_align_fields = false [*.{asm,inc}] indent_size = 8 diff --git a/.github/workflows/base.yml b/.github/workflows/base.yml index 2a5239a9f..c0c57f739 100644 --- a/.github/workflows/base.yml +++ b/.github/workflows/base.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - framework: [ net48, net6.0, net7.0, net8.0 ] + framework: [ net6.0, net7.0, net8.0 ] os: [ ubuntu-latest ] test: [ Streams, PersistentSubscriptions, Operations, UserManagement, ProjectionManagement ] configuration: [ release ] @@ -25,6 +25,10 @@ jobs: - shell: bash run: | git fetch --prune --unshallow + - name: Pull EventStore Image + shell: bash + run: | + docker pull ghcr.io/eventstore/eventstore:${{ inputs.docker-tag }} - name: Install dotnet SDKs uses: actions/setup-dotnet@v3 with: @@ -36,10 +40,6 @@ jobs: shell: bash run: | dotnet build --configuration ${{ matrix.configuration }} --framework ${{ matrix.framework }} src/EventStore.Client.${{ matrix.test }} - - name: Pull EventStore Image - shell: bash - run: | - docker pull ghcr.io/eventstore/eventstore:${{ inputs.docker-tag }} - name: Run Tests shell: bash env: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 67d3166dc..239ea739a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,8 +14,8 @@ jobs: strategy: fail-fast: false matrix: - framework: [ net48, net6.0, net7.0, net8.0 ] - os: [ ubuntu-latest ] + framework: [ net6.0, net7.0, net8.0 ] + os: [ ubuntu-latest, windows-latest ] runs-on: ${{ matrix.os }} name: scan-vulnerabilities/${{ matrix.os }}/${{ matrix.framework }} steps: @@ -27,6 +27,7 @@ jobs: dotnet-version: | 6.0.x 7.0.x + 8.0.x - name: Scan for Vulnerabilities shell: bash run: | @@ -37,19 +38,18 @@ jobs: build-samples: timeout-minutes: 5 - name: build-samples/${{ matrix.os }}/${{ matrix.framework }} + name: build-samples/${{ matrix.framework }} + runs-on: ubuntu-latest strategy: fail-fast: false matrix: - framework: [ net48, net6.0, net7.0, net8.0 ] - os: [ ubuntu-latest ] - runs-on: ${{ matrix.os }} + framework: [ net8.0 ] services: esdb: image: ghcr.io/eventstore/eventstore:lts env: EVENTSTORE_INSECURE: true - EVENTSTORE_MEMDB: true + EVENTSTORE_MEM_DB: false EVENTSTORE_RUN_PROJECTIONS: all EVENTSTORE_START_STANDARD_PROJECTIONS: true ports: @@ -62,8 +62,7 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: | - 6.0.x - 7.0.x + 8.0.x - name: Compile shell: bash run: | @@ -74,11 +73,11 @@ jobs: find samples/ -type f -iname "*.csproj" -print0 | xargs -0L1 dotnet run --framework ${{ matrix.framework }} --project test: - timeout-minutes: 10 + timeout-minutes: 20 strategy: fail-fast: false matrix: - framework: [ net48, net6.0, net7.0 ] + framework: [ net6.0, net7.0, net8.0 ] os: [ ubuntu-latest, windows-latest ] configuration: [ release ] runs-on: ${{ matrix.os }} @@ -95,6 +94,7 @@ jobs: dotnet-version: | 6.0.x 7.0.x + 8.0.x - name: Compile shell: bash run: | @@ -106,7 +106,7 @@ jobs: --logger:"GitHubActions;report-warnings=false" --logger:"console;verbosity=normal" \ --framework ${{ matrix.framework }} \ test/EventStore.Client.Tests - + publish: timeout-minutes: 5 needs: [ vulnerability-scan, test, build-samples ] @@ -132,6 +132,7 @@ jobs: dotnet-version: | 6.0.x 7.0.x + 8.0.x - name: Dotnet Pack shell: bash run: | @@ -158,4 +159,4 @@ jobs: run: | dotnet nuget list source dotnet tool restore - find . -name "*.nupkg" | xargs -n1 dotnet nuget push --api-key=${{ secrets.nuget_key }} --source https://api.nuget.org/v3/index.json --skip-duplicate + find . -name "*.nupkg" | xargs -n1 dotnet nuget push --api-key=${{ secrets.nuget_key }} --source https://api.nuget.org/v3/index.json --skip-duplicate \ No newline at end of file diff --git a/samples/Directory.Build.props b/samples/Directory.Build.props index 4e510486d..54497a508 100644 --- a/samples/Directory.Build.props +++ b/samples/Directory.Build.props @@ -12,4 +12,4 @@ - + \ No newline at end of file diff --git a/src/Directory.Build.props b/src/Directory.Build.props index c40effecf..1921131a9 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -35,9 +35,15 @@ - - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers + + diff --git a/src/EventStore.Client.Common/EpochExtensions.cs b/src/EventStore.Client.Common/EpochExtensions.cs index fa500cdd0..d8d4e113d 100644 --- a/src/EventStore.Client.Common/EpochExtensions.cs +++ b/src/EventStore.Client.Common/EpochExtensions.cs @@ -1,4 +1,4 @@ -using System; +namespace EventStore.Client; static class EpochExtensions { private const long TicksPerMillisecond = 10000; @@ -15,7 +15,7 @@ static class EpochExtensions { private static readonly DateTime UnixEpoch = new(UnixEpochTicks, DateTimeKind.Utc); - private static readonly DateTime UnixEpoch = new(UnixEpochTicks, DateTimeKind.Utc); + public static DateTime FromTicksSinceEpoch(this long value) => new(UnixEpoch.Ticks + value, DateTimeKind.Utc); public static long ToTicksSinceEpoch(this DateTime value) => (value - UnixEpoch).Ticks; } diff --git a/src/EventStore.Client.Streams/EventStoreClient.Append.cs b/src/EventStore.Client.Streams/EventStoreClient.Append.cs index ca9cae353..815aa4196 100644 --- a/src/EventStore.Client.Streams/EventStoreClient.Append.cs +++ b/src/EventStore.Client.Streams/EventStoreClient.Append.cs @@ -4,7 +4,6 @@ using System.Threading; using System.Threading.Tasks; using System.Threading.Channels; -using System.Runtime.CompilerServices; using Google.Protobuf; using EventStore.Client.Streams; using Grpc.Core; diff --git a/src/EventStore.Client/EventStore.Client.csproj b/src/EventStore.Client/EventStore.Client.csproj index a946cee38..2d2e1b40f 100644 --- a/src/EventStore.Client/EventStore.Client.csproj +++ b/src/EventStore.Client/EventStore.Client.csproj @@ -7,11 +7,10 @@ - + - + - diff --git a/src/EventStore.Client/HashCode.cs b/src/EventStore.Client/HashCode.cs index 3bc86fc6d..31f9c2514 100644 --- a/src/EventStore.Client/HashCode.cs +++ b/src/EventStore.Client/HashCode.cs @@ -32,15 +32,6 @@ public HashCode Combine(IEnumerable? values) where T: struct => public HashCode Combine(IEnumerable? values) => (values ?? Enumerable.Empty()).Aggregate(Hash, (previous, value) => previous.Combine(value)); - public static int Combine(Uuid values, byte[] data, byte[] metadata) { - unchecked { - var hash = HashCode.Hash.Combine(values); - hash = hash.Combine(data); - hash = hash.Combine(metadata); - return hash; - } - } - public static implicit operator int(HashCode value) => value._value; - } + } } diff --git a/src/EventStore.Client/Uuid.cs b/src/EventStore.Client/Uuid.cs index ff0c85df8..8f5d2ef45 100644 --- a/src/EventStore.Client/Uuid.cs +++ b/src/EventStore.Client/Uuid.cs @@ -193,28 +193,5 @@ private bool TryWriteGuidBytes(Guid value, Span destination) return true; #endif } - - private static bool TryWriteBytes(Span destination, long value) - { - if (destination.Length < sizeof(long)) - return false; - - Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), value); - return true; - } - - private bool TryWriteGuidBytes(Guid value, Span destination) - { -#if NET - return value.TryWriteBytes(destination); -#else - if (destination.Length < 16) - return false; - - var bytes = value.ToByteArray(); - bytes.CopyTo(destination); - return true; -#endif - } } } diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 12e2cd057..f29b71ca0 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -12,11 +12,12 @@ - - - + + all + runtime; build; native; contentfiles; analyzers + diff --git a/test/EventStore.Client.PersistentSubscriptions.Tests/EventStore.Client.PersistentSubscriptions.Tests.csproj b/test/EventStore.Client.PersistentSubscriptions.Tests/EventStore.Client.PersistentSubscriptions.Tests.csproj index 562ccdc1c..cd15c30d9 100644 --- a/test/EventStore.Client.PersistentSubscriptions.Tests/EventStore.Client.PersistentSubscriptions.Tests.csproj +++ b/test/EventStore.Client.PersistentSubscriptions.Tests/EventStore.Client.PersistentSubscriptions.Tests.csproj @@ -6,7 +6,4 @@ - - - diff --git a/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToStream/get_info.cs b/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToStream/get_info.cs index 8eccd6678..baa1b0fad 100644 --- a/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToStream/get_info.cs +++ b/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToStream/get_info.cs @@ -171,13 +171,13 @@ await Client.SubscribeToStreamAsync( (s, e, r, ct) => { counter++; - switch (counter) { - case 1: s.Nack(PersistentSubscriptionNakEventAction.Park, "Test", e); - break; - case > 10: tcs.TrySetResult(); - break; - } - return Task.CompletedTask; + if (counter == 1) + s.Nack(PersistentSubscriptionNakEventAction.Park, "Test", e); + + if (counter > 10) + tcs.TrySetResult(); + + return Task.CompletedTask; }, userCredentials: TestCredentials.Root ); @@ -195,4 +195,4 @@ await StreamsClient.AppendToStreamAsync( await tcs.Task; } } -} +} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Read/EventBinaryData.cs b/test/EventStore.Client.Streams.Tests/Read/EventBinaryData.cs index 780d016cb..923a40cd1 100644 --- a/test/EventStore.Client.Streams.Tests/Read/EventBinaryData.cs +++ b/test/EventStore.Client.Streams.Tests/Read/EventBinaryData.cs @@ -6,11 +6,7 @@ public bool Equals(EventBinaryData other) => && Data.SequenceEqual(other.Data) && Metadata.SequenceEqual(other.Metadata); -#if NET public override int GetHashCode() => System.HashCode.Combine(Id, Data, Metadata); -#else - public override int GetHashCode() => HashCode.Combine(Id, Data, Metadata); -#endif } public static class EventBinaryDataConverters { @@ -34,4 +30,4 @@ public static EventBinaryData[] ToBinaryData(this IEnumerable sou public static ValueTask ToBinaryData(this IAsyncEnumerable source) => source.DefaultIfEmpty().Select(x => x.ToBinaryData()).ToArrayAsync(); -} +} \ No newline at end of file diff --git a/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj b/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj index 42ca04a99..6c2f73fa1 100644 --- a/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj +++ b/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj @@ -65,7 +65,7 @@ Always - + diff --git a/test/EventStore.Client.Tests/ConnectionStringTests.cs b/test/EventStore.Client.Tests/ConnectionStringTests.cs index 37dc64a0f..553fb37a9 100644 --- a/test/EventStore.Client.Tests/ConnectionStringTests.cs +++ b/test/EventStore.Client.Tests/ConnectionStringTests.cs @@ -391,7 +391,6 @@ static string GetKeyValuePairs( } } #endif - } return string.Join("&", pairs.Select(pair => $"{getKey?.Invoke(pair.Key) ?? pair.Key}={pair.Value}")); } diff --git a/test/EventStore.Client.Tests/EventStore.Client.Tests.csproj b/test/EventStore.Client.Tests/EventStore.Client.Tests.csproj index 4ab5fa410..e6c606150 100644 --- a/test/EventStore.Client.Tests/EventStore.Client.Tests.csproj +++ b/test/EventStore.Client.Tests/EventStore.Client.Tests.csproj @@ -3,19 +3,9 @@ - - - - - - - - - - From dde1fa02c05cc0b55ebce4df55a8baee1ad6f1c6 Mon Sep 17 00:00:00 2001 From: William Date: Fri, 19 Jan 2024 18:38:39 +0400 Subject: [PATCH 32/41] Integrate the recommendations provided by Sergio --- src/Directory.Build.props | 14 +-- .../EpochExtensions.cs | 30 +++--- .../EventStore.Client.Operations.csproj | 6 +- ...tore.Client.PersistentSubscriptions.csproj | 12 +-- ...ntStore.Client.ProjectionManagement.csproj | 6 +- .../EventStore.Client.Streams.csproj | 18 ++-- .../EventStoreClient.Append.cs | 93 ++++++++++--------- .../EventStore.Client.UserManagement.csproj | 12 +-- .../EventStore.Client.csproj | 60 ++++++------ .../Interceptors/TypedExceptionInterceptor.cs | 19 +--- src/EventStore.Client/Uuid.cs | 10 +- test/Directory.Build.props | 40 ++++---- .../EventStore.Client.Operations.Tests.csproj | 2 +- ...lient.PersistentSubscriptions.Tests.csproj | 4 +- ...e.Client.ProjectionManagement.Tests.csproj | 2 +- .../EventStore.Client.Streams.Tests.csproj | 7 +- .../EventStore.Client.Tests.Common.csproj | 52 +++++------ .../Fixtures/EventStoreTestCluster.cs | 7 +- .../FluentDockerServiceExtensions.cs | 49 +++------- .../FluentDocker/TestService.cs | 17 +--- .../EventStore.Client.Tests.csproj | 36 +++---- ...ntStore.Client.UserManagement.Tests.csproj | 4 +- 22 files changed, 229 insertions(+), 271 deletions(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 1921131a9..c12d9449c 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,5 +1,5 @@ - + EventStore.Client @@ -12,8 +12,8 @@ - - + + @@ -30,8 +30,8 @@ - - + + @@ -43,11 +43,11 @@ all runtime; build; native; contentfiles; analyzers - + - + diff --git a/src/EventStore.Client.Common/EpochExtensions.cs b/src/EventStore.Client.Common/EpochExtensions.cs index d8d4e113d..db59e620d 100644 --- a/src/EventStore.Client.Common/EpochExtensions.cs +++ b/src/EventStore.Client.Common/EpochExtensions.cs @@ -1,21 +1,25 @@ namespace EventStore.Client; static class EpochExtensions { - private const long TicksPerMillisecond = 10000; - private const long TicksPerSecond = TicksPerMillisecond * 1000; - private const long TicksPerMinute = TicksPerSecond * 60; - private const long TicksPerHour = TicksPerMinute * 60; - private const long TicksPerDay = TicksPerHour * 24; - private const int DaysPerYear = 365; - private const int DaysPer4Years = DaysPerYear * 4 + 1; - private const int DaysPer100Years = DaysPer4Years * 25 - 1; - private const int DaysPer400Years = DaysPer100Years * 4 + 1; - private const int DaysTo1970 = DaysPer400Years * 4 + DaysPer100Years * 3 + DaysPer4Years * 17 + DaysPerYear; - private const long UnixEpochTicks = DaysTo1970 * TicksPerDay; +#if NET + static readonly DateTime UnixEpoch = DateTime.UnixEpoch; +#else + const long TicksPerMillisecond = 10000; + const long TicksPerSecond = TicksPerMillisecond * 1000; + const long TicksPerMinute = TicksPerSecond * 60; + const long TicksPerHour = TicksPerMinute * 60; + const long TicksPerDay = TicksPerHour * 24; + const int DaysPerYear = 365; + const int DaysPer4Years = DaysPerYear * 4 + 1; + const int DaysPer100Years = DaysPer4Years * 25 - 1; + const int DaysPer400Years = DaysPer100Years * 4 + 1; + const int DaysTo1970 = DaysPer400Years * 4 + DaysPer100Years * 3 + DaysPer4Years * 17 + DaysPerYear; + const long UnixEpochTicks = DaysTo1970 * TicksPerDay; - private static readonly DateTime UnixEpoch = new(UnixEpochTicks, DateTimeKind.Utc); + static readonly DateTime UnixEpoch = new(UnixEpochTicks, DateTimeKind.Utc); +#endif public static DateTime FromTicksSinceEpoch(this long value) => new(UnixEpoch.Ticks + value, DateTimeKind.Utc); public static long ToTicksSinceEpoch(this DateTime value) => (value - UnixEpoch).Ticks; -} +} \ No newline at end of file diff --git a/src/EventStore.Client.Operations/EventStore.Client.Operations.csproj b/src/EventStore.Client.Operations/EventStore.Client.Operations.csproj index 2a351190d..37299e1a1 100644 --- a/src/EventStore.Client.Operations/EventStore.Client.Operations.csproj +++ b/src/EventStore.Client.Operations/EventStore.Client.Operations.csproj @@ -1,6 +1,6 @@  - - The GRPC client API for Event Store Operations, e.g., Scavenging. Get the open source or commercial versions of Event Store server from https://eventstore.com/ - + + The GRPC client API for Event Store Operations, e.g., Scavenging. Get the open source or commercial versions of Event Store server from https://eventstore.com/ + diff --git a/src/EventStore.Client.PersistentSubscriptions/EventStore.Client.PersistentSubscriptions.csproj b/src/EventStore.Client.PersistentSubscriptions/EventStore.Client.PersistentSubscriptions.csproj index 8d6521549..cb7e38ce1 100644 --- a/src/EventStore.Client.PersistentSubscriptions/EventStore.Client.PersistentSubscriptions.csproj +++ b/src/EventStore.Client.PersistentSubscriptions/EventStore.Client.PersistentSubscriptions.csproj @@ -1,9 +1,9 @@  - - The GRPC client API for Event Store Persistent Subscriptions. Get the open source or commercial versions of Event Store server from https://eventstore.com/ - - - - + + The GRPC client API for Event Store Persistent Subscriptions. Get the open source or commercial versions of Event Store server from https://eventstore.com/ + + + + diff --git a/src/EventStore.Client.ProjectionManagement/EventStore.Client.ProjectionManagement.csproj b/src/EventStore.Client.ProjectionManagement/EventStore.Client.ProjectionManagement.csproj index 63ce0af0d..678656cff 100644 --- a/src/EventStore.Client.ProjectionManagement/EventStore.Client.ProjectionManagement.csproj +++ b/src/EventStore.Client.ProjectionManagement/EventStore.Client.ProjectionManagement.csproj @@ -1,6 +1,6 @@  - - The GRPC client API for managing Event Store Projections. Get the open source or commercial versions of Event Store server from https://eventstore.com/ - + + The GRPC client API for managing Event Store Projections. Get the open source or commercial versions of Event Store server from https://eventstore.com/ + diff --git a/src/EventStore.Client.Streams/EventStore.Client.Streams.csproj b/src/EventStore.Client.Streams/EventStore.Client.Streams.csproj index 41ccec721..a9c2969f2 100644 --- a/src/EventStore.Client.Streams/EventStore.Client.Streams.csproj +++ b/src/EventStore.Client.Streams/EventStore.Client.Streams.csproj @@ -1,12 +1,12 @@  - - The GRPC client API for Event Store Streams. Get the open source or commercial versions of Event Store server from https://eventstore.com/ - - - - - - - + + The GRPC client API for Event Store Streams. Get the open source or commercial versions of Event Store server from https://eventstore.com/ + + + + + + + diff --git a/src/EventStore.Client.Streams/EventStoreClient.Append.cs b/src/EventStore.Client.Streams/EventStoreClient.Append.cs index 815aa4196..6cb2c9fd5 100644 --- a/src/EventStore.Client.Streams/EventStoreClient.Append.cs +++ b/src/EventStore.Client.Streams/EventStoreClient.Append.cs @@ -45,7 +45,7 @@ public async Task AppendToStreamAsync( new AppendReq { Options = new AppendReq.Types.Options { StreamIdentifier = streamName, - Revision = expectedRevision + Revision = expectedRevision } }, eventData, options, deadline, userCredentials, cancellationToken); @@ -113,8 +113,8 @@ private async ValueTask AppendToStreamInternal( header.Options.StreamIdentifier, e.EventId, e.Type); await call.RequestStream.WriteAsync(new AppendReq { ProposedMessage = new AppendReq.Types.ProposedMessage { - Id = e.EventId.ToDto(), - Data = ByteString.CopyFrom(e.Data.Span), + Id = e.EventId.ToDto(), + Data = ByteString.CopyFrom(e.Data.Span), CustomMetadata = ByteString.CopyFrom(e.Metadata.Span), Metadata = { {Constants.Metadata.Type, e.Type}, @@ -142,10 +142,10 @@ await call.RequestStream.WriteAsync(new AppendReq { } else { if (response.WrongExpectedVersion != null) { var actualStreamRevision = response.WrongExpectedVersion.CurrentRevisionOptionCase switch { - AppendResp.Types.WrongExpectedVersion.CurrentRevisionOptionOneofCase.CurrentNoStream => - StreamRevision.None, - _ => new StreamRevision(response.WrongExpectedVersion.CurrentRevision) - }; + AppendResp.Types.WrongExpectedVersion.CurrentRevisionOptionOneofCase.CurrentNoStream => + StreamRevision.None, + _ => new StreamRevision(response.WrongExpectedVersion.CurrentRevision) + }; _log.LogDebug( "Append to stream failed with Wrong Expected Version - {streamName}/{expectedRevision}/{currentRevision}", @@ -154,7 +154,7 @@ await call.RequestStream.WriteAsync(new AppendReq { if (operationOptions.ThrowOnAppendFailure) { if (response.WrongExpectedVersion.ExpectedRevisionOptionCase == AppendResp.Types - .WrongExpectedVersion.ExpectedRevisionOptionOneofCase.ExpectedRevision) { + .WrongExpectedVersion.ExpectedRevisionOptionOneofCase.ExpectedRevision) { throw new WrongExpectedVersionException(header.Options.StreamIdentifier!, new StreamRevision(response.WrongExpectedVersion.ExpectedRevision), actualStreamRevision); @@ -175,7 +175,7 @@ await call.RequestStream.WriteAsync(new AppendReq { } if (response.WrongExpectedVersion.ExpectedRevisionOptionCase == AppendResp.Types - .WrongExpectedVersion.ExpectedRevisionOptionOneofCase.ExpectedRevision) { + .WrongExpectedVersion.ExpectedRevisionOptionOneofCase.ExpectedRevision) { writeResult = new WrongExpectedVersionResult(header.Options.StreamIdentifier!, new StreamRevision(response.WrongExpectedVersion.ExpectedRevision), actualStreamRevision); @@ -196,34 +196,34 @@ await call.RequestStream.WriteAsync(new AppendReq { private class StreamAppender : IDisposable { - private readonly EventStoreClientSettings _settings; - private readonly CancellationToken _cancellationToken; - private readonly Action _onException; - private readonly Channel _channel; + private readonly EventStoreClientSettings _settings; + private readonly CancellationToken _cancellationToken; + private readonly Action _onException; + private readonly Channel _channel; private readonly ConcurrentDictionary> _pendingRequests; private readonly Task?> _callTask; public StreamAppender(EventStoreClientSettings settings, - Task?> callTask, CancellationToken cancellationToken, - Action onException) { - _settings = settings; - _callTask = callTask; + Task?> callTask, CancellationToken cancellationToken, + Action onException) { + _settings = settings; + _callTask = callTask; _cancellationToken = cancellationToken; - _onException = onException; - _channel = System.Threading.Channels.Channel.CreateBounded(10000); - _pendingRequests = new ConcurrentDictionary>(); - _ = Task.Factory.StartNew(Send); - _ = Task.Factory.StartNew(Receive); + _onException = onException; + _channel = System.Threading.Channels.Channel.CreateBounded(10000); + _pendingRequests = new ConcurrentDictionary>(); + _ = Task.Factory.StartNew(Send); + _ = Task.Factory.StartNew(Receive); } public ValueTask Append(string streamName, StreamRevision expectedStreamPosition, - IEnumerable events, TimeSpan? timeoutAfter, CancellationToken cancellationToken = default) => + IEnumerable events, TimeSpan? timeoutAfter, CancellationToken cancellationToken = default) => AppendInternal(BatchAppendReq.Types.Options.Create(streamName, expectedStreamPosition, timeoutAfter), events, cancellationToken); public ValueTask Append(string streamName, StreamState expectedStreamState, - IEnumerable events, TimeSpan? timeoutAfter, CancellationToken cancellationToken = default) => + IEnumerable events, TimeSpan? timeoutAfter, CancellationToken cancellationToken = default) => AppendInternal(BatchAppendReq.Types.Options.Create(streamName, expectedStreamState, timeoutAfter), events, cancellationToken); @@ -242,7 +242,7 @@ private async Task Receive() { } await foreach (var response in call.ResponseStream.ReadAllAsync(_cancellationToken) - .ConfigureAwait(false)) { + .ConfigureAwait(false)) { if (!_pendingRequests.TryRemove(Uuid.FromDto(response.CorrelationId), out var writeResult)) { continue; // TODO: Log? } @@ -271,7 +271,7 @@ private async Task Send() { throw new NotSupportedException("Server does not support batch append"); await foreach (var appendRequest in ReadAllAsync(_channel.Reader, _cancellationToken) - .ConfigureAwait(false)) { + .ConfigureAwait(false)) { await call.RequestStream.WriteAsync(appendRequest).ConfigureAwait(false); } @@ -279,9 +279,9 @@ private async Task Send() { } private async ValueTask AppendInternal(BatchAppendReq.Types.Options options, - IEnumerable events, CancellationToken cancellationToken) { - var batchSize = 0; - var correlationId = Uuid.NewUuid(); + IEnumerable events, CancellationToken cancellationToken) { + var batchSize = 0; + var correlationId = Uuid.NewUuid(); var correlationIdDto = correlationId.ToDto(); var complete = _pendingRequests.GetOrAdd(correlationId, new TaskCompletionSource()); @@ -298,13 +298,13 @@ private async ValueTask AppendInternal(BatchAppendReq.Types.Option return await complete.Task.ConfigureAwait(false); IEnumerable GetRequests() { - bool first = true; - var proposedMessages = new List(); + bool first = true; + var proposedMessages = new List(); foreach (var @event in events) { var proposedMessage = new BatchAppendReq.Types.ProposedMessage { - Data = ByteString.CopyFrom(@event.Data.Span), + Data = ByteString.CopyFrom(@event.Data.Span), CustomMetadata = ByteString.CopyFrom(@event.Metadata.Span), - Id = @event.EventId.ToDto(), + Id = @event.EventId.ToDto(), Metadata = { {Constants.Metadata.Type, @event.Type}, {Constants.Metadata.ContentType, @event.ContentType} @@ -320,8 +320,8 @@ IEnumerable GetRequests() { yield return new BatchAppendReq { ProposedMessages = {proposedMessages}, - CorrelationId = correlationIdDto, - Options = first ? options : null + CorrelationId = correlationIdDto, + Options = first ? options : null }; first = false; proposedMessages.Clear(); @@ -330,9 +330,9 @@ IEnumerable GetRequests() { yield return new BatchAppendReq { ProposedMessages = {proposedMessages}, - IsFinal = true, - CorrelationId = correlationIdDto, - Options = first ? options : null + IsFinal = true, + CorrelationId = correlationIdDto, + Options = first ? options : null }; } } @@ -341,15 +341,18 @@ public void Dispose() { _channel.Writer.TryComplete(); } } - private static async IAsyncEnumerable ReadAllAsync(ChannelReader reader, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - while (await reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) - { - while (reader.TryRead(out T? item)) - { + + private static async IAsyncEnumerable ReadAllAsync(ChannelReader reader, [EnumeratorCancellation] CancellationToken cancellationToken = default) { +#if NET + await foreach (var item in reader.ReadAllAsync(cancellationToken)) + yield return item; +#else + while (await reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { + while (reader.TryRead(out T? item)) { yield return item; } } +#endif } } -} +} \ No newline at end of file diff --git a/src/EventStore.Client.UserManagement/EventStore.Client.UserManagement.csproj b/src/EventStore.Client.UserManagement/EventStore.Client.UserManagement.csproj index 68b8d5098..2c9308e2e 100644 --- a/src/EventStore.Client.UserManagement/EventStore.Client.UserManagement.csproj +++ b/src/EventStore.Client.UserManagement/EventStore.Client.UserManagement.csproj @@ -1,9 +1,9 @@  - - The GRPC client API for managing users in Event Store. Get the open source or commercial versions of Event Store server from https://eventstore.com/ - - - - + + The GRPC client API for managing users in Event Store. Get the open source or commercial versions of Event Store server from https://eventstore.com/ + + + + diff --git a/src/EventStore.Client/EventStore.Client.csproj b/src/EventStore.Client/EventStore.Client.csproj index 2d2e1b40f..71fadf0c2 100644 --- a/src/EventStore.Client/EventStore.Client.csproj +++ b/src/EventStore.Client/EventStore.Client.csproj @@ -1,33 +1,33 @@  - - EventStore.Client - The base GRPC client library for Event Store. Get the open source or commercial versions of Event Store server from https://eventstore.com/ - EventStore.Client.Grpc - - - - - - - - - - - - - - - - - - - - - - - - - - + + EventStore.Client + The base GRPC client library for Event Store. Get the open source or commercial versions of Event Store server from https://eventstore.com/ + EventStore.Client.Grpc + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/EventStore.Client/Interceptors/TypedExceptionInterceptor.cs b/src/EventStore.Client/Interceptors/TypedExceptionInterceptor.cs index 0781eb9ef..d24dd2a8e 100644 --- a/src/EventStore.Client/Interceptors/TypedExceptionInterceptor.cs +++ b/src/EventStore.Client/Interceptors/TypedExceptionInterceptor.cs @@ -1,4 +1,3 @@ - using System.Diagnostics.CodeAnalysis; using Grpc.Core; using Grpc.Core.Interceptors; @@ -13,14 +12,12 @@ class TypedExceptionInterceptor : Interceptor { [Exceptions.NotLeader] = ex => ex.ToNotLeaderException(), }; -#if NET48 public TypedExceptionInterceptor(Dictionary> customExceptionMap) { - var map = new Dictionary>(DefaultExceptionMap); +#if NET48 + var map = new Dictionary>(DefaultExceptionMap.Concat(customExceptionMap).ToDictionary(x => x.Key, x => x.Value)); #else - public TypedExceptionInterceptor(Dictionary> customExceptionMap) { var map = new Dictionary>(DefaultExceptionMap.Concat(customExceptionMap)); #endif - ConvertRpcException = rpcEx => { if (rpcEx.TryMapException(map, out var ex)) throw ex; @@ -122,7 +119,6 @@ public static NotAuthenticatedException ToNotAuthenticatedException(this RpcExce public static RpcException ToDeadlineExceededRpcException(this RpcException exception) => new(new Status(DeadlineExceeded, exception.Status.Detail, exception.Status.DebugException)); -#if NET48 public static bool TryMapException(this RpcException exception, Dictionary> map, out Exception createdException) { if (exception.Trailers.TryGetValue(Exceptions.ExceptionKey, out var key) && map.TryGetValue(key!, out var factory)) { createdException = factory.Invoke(exception); @@ -132,17 +128,6 @@ public static bool TryMapException(this RpcException exception, Dictionary> map, [MaybeNullWhen(false)] out Exception createdException) { - if (exception.Trailers.TryGetValue(Exceptions.ExceptionKey, out var key) && map.TryGetValue(key!, out var factory)) { - createdException = factory.Invoke(exception); - return true; - } - - createdException = null; - return false; - } -#endif } class ExceptionConverterStreamReader(IAsyncStreamReader reader, Func convertException) : IAsyncStreamReader { diff --git a/src/EventStore.Client/Uuid.cs b/src/EventStore.Client/Uuid.cs index 8f5d2ef45..6c96b1e2d 100644 --- a/src/EventStore.Client/Uuid.cs +++ b/src/EventStore.Client/Uuid.cs @@ -84,7 +84,13 @@ private Uuid(Guid value) { [MethodImpl(MethodImplOptions.AggressiveInlining)] private static long BitConverterToInt64(ReadOnlySpan value) - => Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(value)); + { +#if NET + return BitConverter.ToInt64(value); +#else + return Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(value)); +#endif + } private Uuid(string value) : this(value == null ? throw new ArgumentNullException(nameof(value)) @@ -183,7 +189,7 @@ private static bool TryWriteBytes(Span destination, long value) private bool TryWriteGuidBytes(Guid value, Span destination) { #if NET - return value.TryWriteBytes(destination); + return value.TryWriteBytes(destination); #else if (destination.Length < 16) return false; diff --git a/test/Directory.Build.props b/test/Directory.Build.props index f29b71ca0..0357513c2 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -1,5 +1,5 @@ - + true @@ -7,13 +7,13 @@ - - - + + + - - - + + + all runtime; build; native; contentfiles; analyzers @@ -21,20 +21,20 @@ - - - - - - - + + + + + + + - - - + + + - - - + + + diff --git a/test/EventStore.Client.Operations.Tests/EventStore.Client.Operations.Tests.csproj b/test/EventStore.Client.Operations.Tests/EventStore.Client.Operations.Tests.csproj index 1febb6cfb..d43bdceb5 100644 --- a/test/EventStore.Client.Operations.Tests/EventStore.Client.Operations.Tests.csproj +++ b/test/EventStore.Client.Operations.Tests/EventStore.Client.Operations.Tests.csproj @@ -1,6 +1,6 @@ - + diff --git a/test/EventStore.Client.PersistentSubscriptions.Tests/EventStore.Client.PersistentSubscriptions.Tests.csproj b/test/EventStore.Client.PersistentSubscriptions.Tests/EventStore.Client.PersistentSubscriptions.Tests.csproj index cd15c30d9..04cf6634b 100644 --- a/test/EventStore.Client.PersistentSubscriptions.Tests/EventStore.Client.PersistentSubscriptions.Tests.csproj +++ b/test/EventStore.Client.PersistentSubscriptions.Tests/EventStore.Client.PersistentSubscriptions.Tests.csproj @@ -1,9 +1,9 @@  - + - + diff --git a/test/EventStore.Client.ProjectionManagement.Tests/EventStore.Client.ProjectionManagement.Tests.csproj b/test/EventStore.Client.ProjectionManagement.Tests/EventStore.Client.ProjectionManagement.Tests.csproj index dac52c701..31381e212 100644 --- a/test/EventStore.Client.ProjectionManagement.Tests/EventStore.Client.ProjectionManagement.Tests.csproj +++ b/test/EventStore.Client.ProjectionManagement.Tests/EventStore.Client.ProjectionManagement.Tests.csproj @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/EventStore.Client.Streams.Tests.csproj b/test/EventStore.Client.Streams.Tests/EventStore.Client.Streams.Tests.csproj index 18e956804..caf586457 100644 --- a/test/EventStore.Client.Streams.Tests/EventStore.Client.Streams.Tests.csproj +++ b/test/EventStore.Client.Streams.Tests/EventStore.Client.Streams.Tests.csproj @@ -1,12 +1,9 @@  - + - + - - - diff --git a/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj b/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj index 6c2f73fa1..f666f3871 100644 --- a/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj +++ b/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj @@ -4,40 +4,40 @@ - - - - - - + + + + + + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + - + - + certs\%(RecursiveDir)/%(FileName)%(Extension) @@ -67,6 +67,6 @@ - + diff --git a/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestCluster.cs b/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestCluster.cs index 682995ae6..ff3ec644b 100644 --- a/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestCluster.cs +++ b/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestCluster.cs @@ -48,11 +48,6 @@ protected override CompositeBuilder Configure() { } protected override async Task OnServiceStarted() { -#if NET48 - Service.WaitUntilNodesAreHealthy("esdb-node", TimeSpan.FromSeconds(60)); -#else - await Service.WaitUntilNodesAreHealthy("esdb-node", TimeSpan.FromSeconds(60)); -#endif - await Task.CompletedTask; + await Service.WaitUntilNodesAreHealthy("esdb-node", TimeSpan.FromSeconds(60)); } } diff --git a/test/EventStore.Client.Tests.Common/FluentDocker/FluentDockerServiceExtensions.cs b/test/EventStore.Client.Tests.Common/FluentDocker/FluentDockerServiceExtensions.cs index 2e2f74fc3..c4773cede 100644 --- a/test/EventStore.Client.Tests.Common/FluentDocker/FluentDockerServiceExtensions.cs +++ b/test/EventStore.Client.Tests.Common/FluentDocker/FluentDockerServiceExtensions.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + using Ductus.FluentDocker.Common; using Ductus.FluentDocker.Model.Containers; using Ductus.FluentDocker.Services; @@ -27,55 +29,34 @@ public static async ValueTask WaitUntilNodesAreHealthy(this IContainerService se await WaitUntilNodesAreHealthy(service, cts.Token); } -#if NET - public static async Task WaitUntilNodesAreHealthy(this ICompositeService service, IEnumerable services, CancellationToken cancellationToken) -#else - public static void WaitUntilNodesAreHealthy(this ICompositeService service, IEnumerable services, CancellationToken cancellationToken) -#endif -{ + public static async Task WaitUntilNodesAreHealthy(this ICompositeService service, IEnumerable services, CancellationToken cancellationToken) { var nodes = service.Containers.Where(x => services.Contains(x.Name)); - #if NET await Parallel.ForEachAsync(nodes, cancellationToken, async (node, ct) => await node.WaitUntilNodesAreHealthy(ct)); #else - Parallel.ForEach( - nodes, - node => { - node.WaitUntilNodesAreHealthy(cancellationToken).GetAwaiter().GetResult(); - } - ); + Parallel.ForEach( + nodes, + node => { node.WaitUntilNodesAreHealthy(cancellationToken).GetAwaiter().GetResult(); } + ); #endif - } + } -#if NET - public static async Task WaitUntilNodesAreHealthy(this ICompositeService service, string serviceNamePrefix, CancellationToken cancellationToken) -#else - public static void WaitUntilNodesAreHealthy(this ICompositeService service, string serviceNamePrefix, CancellationToken cancellationToken) -#endif -{ + public static async Task WaitUntilNodesAreHealthy(this ICompositeService service, string serviceNamePrefix, CancellationToken cancellationToken) { var nodes = service.Containers.Where(x => x.Name.StartsWith(serviceNamePrefix)); + #if NET await Parallel.ForEachAsync(nodes, cancellationToken, async (node, ct) => await node.WaitUntilNodesAreHealthy(ct)); #else - Parallel.ForEach( - nodes, - node => { - node.WaitUntilNodesAreHealthy(cancellationToken).GetAwaiter().GetResult(); - } - ); + Parallel.ForEach( + nodes, + node => { node.WaitUntilNodesAreHealthy(cancellationToken).GetAwaiter().GetResult(); } + ); #endif } -#if NET public static async Task WaitUntilNodesAreHealthy(this ICompositeService service, string serviceNamePrefix, TimeSpan timeout) { -#else - public static void WaitUntilNodesAreHealthy(this ICompositeService service, string serviceNamePrefix, TimeSpan timeout) { -#endif using var cts = new CancellationTokenSource(timeout); -#if NET + await WaitUntilNodesAreHealthy(service, serviceNamePrefix, cts.Token); -#else - WaitUntilNodesAreHealthy(service, serviceNamePrefix, cts.Token); -#endif } } diff --git a/test/EventStore.Client.Tests.Common/FluentDocker/TestService.cs b/test/EventStore.Client.Tests.Common/FluentDocker/TestService.cs index 7ed8c3f27..890f4e9b6 100644 --- a/test/EventStore.Client.Tests.Common/FluentDocker/TestService.cs +++ b/test/EventStore.Client.Tests.Common/FluentDocker/TestService.cs @@ -12,11 +12,7 @@ public interface ITestService : IAsyncDisposable { Task Stop(); Task Restart(TimeSpan delay); -#if NET - Task Restart() => Restart(TimeSpan.Zero); -#else Task Restart(); -#endif void ReportStatus(); } @@ -30,12 +26,10 @@ public abstract class TestService : ITestService where TServ INetworkService? Network { get; set; } = null!; -#if !NET - Task ITestService.Restart() + public Task Restart() { return Restart(TimeSpan.Zero); } -#endif public virtual async Task Start() { Logger.Information("Container service starting"); @@ -129,12 +123,6 @@ void ReportContainerStatus(IContainerService service) { } } -#if !NET - public virtual ValueTask DisposeAsync() - { - return DisposeAsync(); - } -#else public virtual ValueTask DisposeAsync() { try { Network?.Dispose(); @@ -150,9 +138,8 @@ public virtual ValueTask DisposeAsync() { throw new FluentDockerException("Failed to dispose of container service", ex); } - return ValueTask.CompletedTask; + return default; } -#endif protected abstract TBuilder Configure(); diff --git a/test/EventStore.Client.Tests/EventStore.Client.Tests.csproj b/test/EventStore.Client.Tests/EventStore.Client.Tests.csproj index e6c606150..494e5e243 100644 --- a/test/EventStore.Client.Tests/EventStore.Client.Tests.csproj +++ b/test/EventStore.Client.Tests/EventStore.Client.Tests.csproj @@ -1,32 +1,32 @@  - + - + - + - + - - - - - - - - - all - runtime; build; native; contentfiles; analyzers - - + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + - - + + diff --git a/test/EventStore.Client.UserManagement.Tests/EventStore.Client.UserManagement.Tests.csproj b/test/EventStore.Client.UserManagement.Tests/EventStore.Client.UserManagement.Tests.csproj index d4b52e67f..abaf5e7bf 100644 --- a/test/EventStore.Client.UserManagement.Tests/EventStore.Client.UserManagement.Tests.csproj +++ b/test/EventStore.Client.UserManagement.Tests/EventStore.Client.UserManagement.Tests.csproj @@ -4,9 +4,9 @@ EventStore.Client.Tests - + - + \ No newline at end of file From d89a08352644f88a33b304a7ec873775f101148a Mon Sep 17 00:00:00 2001 From: William Chong Date: Tue, 23 Jan 2024 17:00:24 +0400 Subject: [PATCH 33/41] Try using HttpHandler --- src/EventStore.Client/ChannelFactory.cs | 40 +++++++++++-------- .../EventStore.Client.csproj | 3 +- .../Base/EventStoreClientFixtureBase.cs | 2 +- .../Base/EventStoreTestServerCluster.cs | 2 +- 4 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src/EventStore.Client/ChannelFactory.cs b/src/EventStore.Client/ChannelFactory.cs index 594394469..6c9e5aa8a 100644 --- a/src/EventStore.Client/ChannelFactory.cs +++ b/src/EventStore.Client/ChannelFactory.cs @@ -1,7 +1,10 @@ using System; +using System.Net; using EndPoint = System.Net.EndPoint; using System.Net.Http; +using System.Security.Authentication; using Grpc.Net.Client; +using Grpc.Net.Client.Web; using TChannel = Grpc.Net.Client.GrpcChannel; namespace EventStore.Client { @@ -16,18 +19,23 @@ public static TChannel CreateChannel(EventStoreClientSettings settings, EndPoint AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); } - return TChannel.ForAddress(address, new GrpcChannelOptions { - HttpClient = new HttpClient(CreateHandler(), true) { - Timeout = System.Threading.Timeout.InfiniteTimeSpan, + return TChannel.ForAddress( + address, + new GrpcChannelOptions { #if NET + HttpClient = new HttpClient(CreateHandler(), true) { + Timeout = System.Threading.Timeout.InfiniteTimeSpan, DefaultRequestVersion = new Version(2, 0), + }, +#else + HttpHandler = CreateHandler(), #endif - }, - LoggerFactory = settings.LoggerFactory, - Credentials = settings.ChannelCredentials, - DisposeHttpClient = true, - MaxReceiveMessageSize = MaxReceiveMessageLength - }); + LoggerFactory = settings.LoggerFactory, + Credentials = settings.ChannelCredentials, + DisposeHttpClient = true, + MaxReceiveMessageSize = MaxReceiveMessageLength + } + ); HttpMessageHandler CreateHandler() { if (settings.CreateHttpMessageHandler != null) { @@ -41,14 +49,14 @@ HttpMessageHandler CreateHandler() { EnableMultipleHttp2Connections = true }; #else - return new WinHttpHandler { - TcpKeepAliveEnabled = true, - TcpKeepAliveTime = settings.ConnectivitySettings.KeepAliveTimeout, - TcpKeepAliveInterval = settings.ConnectivitySettings.KeepAliveInterval, - EnableMultipleHttp2Connections = true - }; + return new WinHttpHandler { + TcpKeepAliveEnabled = true, + TcpKeepAliveTime = settings.ConnectivitySettings.KeepAliveTimeout, + TcpKeepAliveInterval = settings.ConnectivitySettings.KeepAliveInterval, + EnableMultipleHttp2Connections = true, + }; #endif } } } -} +} \ No newline at end of file diff --git a/src/EventStore.Client/EventStore.Client.csproj b/src/EventStore.Client/EventStore.Client.csproj index 71fadf0c2..ff4f6047f 100644 --- a/src/EventStore.Client/EventStore.Client.csproj +++ b/src/EventStore.Client/EventStore.Client.csproj @@ -11,6 +11,7 @@ + @@ -25,7 +26,7 @@ - + diff --git a/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreClientFixtureBase.cs b/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreClientFixtureBase.cs index ba93a7ad5..bf3b3670a 100644 --- a/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreClientFixtureBase.cs +++ b/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreClientFixtureBase.cs @@ -14,7 +14,7 @@ namespace EventStore.Client; public abstract class EventStoreClientFixtureBase : IAsyncLifetime { public const string TestEventType = "-"; - const string ConnectionStringSingle = "esdb://admin:changeit@localhost:2113/?tlsVerifyCert=false"; + const string ConnectionStringSingle = "esdb://admin:changeit@localhost:2113/?tlsVerifyCert=false&tls=true"; const string ConnectionStringCluster = "esdb://admin:changeit@localhost:2113,localhost:2112,localhost:2111?tls=true&tlsVerifyCert=false"; static readonly Subject LogEventSubject = new(); diff --git a/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServerCluster.cs b/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServerCluster.cs index ceb263e15..a3a7f45c8 100644 --- a/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServerCluster.cs +++ b/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServerCluster.cs @@ -18,7 +18,7 @@ public EventStoreTestServerCluster( IDictionary? envOverrides ) { envOverrides ??= new Dictionary(); - envOverrides["ES_CERTS_CLUSTER"] = hostCertificatePath; + envOverrides["ES_CERTS_CLUSTER"] = hostCertificatePath; _eventStoreCluster = BuildCluster(envOverrides); From 4e8ca3ad1d8cfd585ca6422d2c40cda52dbfe6a4 Mon Sep 17 00:00:00 2001 From: josephcummings Date: Tue, 23 Jan 2024 18:17:38 +0000 Subject: [PATCH 34/41] Make use of EventStoreClientSettings.CreateHttpMessageHandler in ChannelFactory --- src/EventStore.Client/ChannelFactory.cs | 54 +++++++++---------------- 1 file changed, 19 insertions(+), 35 deletions(-) diff --git a/src/EventStore.Client/ChannelFactory.cs b/src/EventStore.Client/ChannelFactory.cs index 6c9e5aa8a..2fbb60874 100644 --- a/src/EventStore.Client/ChannelFactory.cs +++ b/src/EventStore.Client/ChannelFactory.cs @@ -1,10 +1,7 @@ -using System; -using System.Net; -using EndPoint = System.Net.EndPoint; using System.Net.Http; -using System.Security.Authentication; using Grpc.Net.Client; -using Grpc.Net.Client.Web; +using System.Net.Security; +using EndPoint = System.Net.EndPoint; using TChannel = Grpc.Net.Client.GrpcChannel; namespace EventStore.Client { @@ -19,44 +16,31 @@ public static TChannel CreateChannel(EventStoreClientSettings settings, EndPoint AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); } - return TChannel.ForAddress( - address, + var grpcChannelOptions = new GrpcChannelOptions { -#if NET - HttpClient = new HttpClient(CreateHandler(), true) { - Timeout = System.Threading.Timeout.InfiniteTimeSpan, - DefaultRequestVersion = new Version(2, 0), - }, -#else - HttpHandler = CreateHandler(), -#endif LoggerFactory = settings.LoggerFactory, Credentials = settings.ChannelCredentials, DisposeHttpClient = true, MaxReceiveMessageSize = MaxReceiveMessageLength - } - ); - - HttpMessageHandler CreateHandler() { - if (settings.CreateHttpMessageHandler != null) { - return settings.CreateHttpMessageHandler.Invoke(); - } + }; + var httpMessageHandler = settings.CreateHttpMessageHandler?.Invoke()!; #if NET - return new SocketsHttpHandler { - KeepAlivePingDelay = settings.ConnectivitySettings.KeepAliveInterval, - KeepAlivePingTimeout = settings.ConnectivitySettings.KeepAliveTimeout, - EnableMultipleHttp2Connections = true - }; + grpcChannelOptions.HttpClient = new HttpClient( + httpMessageHandler, + true + ) { + Timeout = System.Threading.Timeout.InfiniteTimeSpan, + DefaultRequestVersion = new Version(2, 0), + }; #else - return new WinHttpHandler { - TcpKeepAliveEnabled = true, - TcpKeepAliveTime = settings.ConnectivitySettings.KeepAliveTimeout, - TcpKeepAliveInterval = settings.ConnectivitySettings.KeepAliveInterval, - EnableMultipleHttp2Connections = true, - }; + grpcChannelOptions.HttpHandler = httpMessageHandler; #endif - } + + return TChannel.ForAddress( + address, + grpcChannelOptions + ); } } -} \ No newline at end of file +} From 5504336a40af100021e58e719cdc1f898d61de33 Mon Sep 17 00:00:00 2001 From: josephcummings Date: Tue, 23 Jan 2024 18:21:11 +0000 Subject: [PATCH 35/41] Cleanup --- src/EventStore.Client/EventStore.Client.csproj | 1 - .../Fixtures/Base/EventStoreClientFixtureBase.cs | 2 +- .../Fixtures/Base/EventStoreTestServerCluster.cs | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/EventStore.Client/EventStore.Client.csproj b/src/EventStore.Client/EventStore.Client.csproj index ff4f6047f..3ab3a3faf 100644 --- a/src/EventStore.Client/EventStore.Client.csproj +++ b/src/EventStore.Client/EventStore.Client.csproj @@ -11,7 +11,6 @@ - diff --git a/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreClientFixtureBase.cs b/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreClientFixtureBase.cs index bf3b3670a..522495198 100644 --- a/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreClientFixtureBase.cs +++ b/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreClientFixtureBase.cs @@ -14,7 +14,7 @@ namespace EventStore.Client; public abstract class EventStoreClientFixtureBase : IAsyncLifetime { public const string TestEventType = "-"; - const string ConnectionStringSingle = "esdb://admin:changeit@localhost:2113/?tlsVerifyCert=false&tls=true"; + const string ConnectionStringSingle = "esdb://admin:changeit@localhost:2113/?tls=true&tlsVerifyCert=false"; const string ConnectionStringCluster = "esdb://admin:changeit@localhost:2113,localhost:2112,localhost:2111?tls=true&tlsVerifyCert=false"; static readonly Subject LogEventSubject = new(); diff --git a/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServerCluster.cs b/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServerCluster.cs index a3a7f45c8..ceb263e15 100644 --- a/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServerCluster.cs +++ b/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServerCluster.cs @@ -18,7 +18,7 @@ public EventStoreTestServerCluster( IDictionary? envOverrides ) { envOverrides ??= new Dictionary(); - envOverrides["ES_CERTS_CLUSTER"] = hostCertificatePath; + envOverrides["ES_CERTS_CLUSTER"] = hostCertificatePath; _eventStoreCluster = BuildCluster(envOverrides); From 50d1c5b34dc5d2906ae6597e47eed48fc6955066 Mon Sep 17 00:00:00 2001 From: josephcummings Date: Tue, 23 Jan 2024 18:27:08 +0000 Subject: [PATCH 36/41] Update ChannelFactory, handle null settings.CreateHttpMessageHandler --- src/EventStore.Client/ChannelFactory.cs | 60 ++++++++++++++++--------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/src/EventStore.Client/ChannelFactory.cs b/src/EventStore.Client/ChannelFactory.cs index 2fbb60874..951aaf055 100644 --- a/src/EventStore.Client/ChannelFactory.cs +++ b/src/EventStore.Client/ChannelFactory.cs @@ -16,31 +16,49 @@ public static TChannel CreateChannel(EventStoreClientSettings settings, EndPoint AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); } - var grpcChannelOptions = - new GrpcChannelOptions { - LoggerFactory = settings.LoggerFactory, - Credentials = settings.ChannelCredentials, - DisposeHttpClient = true, - MaxReceiveMessageSize = MaxReceiveMessageLength - }; - - var httpMessageHandler = settings.CreateHttpMessageHandler?.Invoke()!; + return TChannel.ForAddress(address, new GrpcChannelOptions { #if NET - grpcChannelOptions.HttpClient = new HttpClient( - httpMessageHandler, - true - ) { - Timeout = System.Threading.Timeout.InfiniteTimeSpan, - DefaultRequestVersion = new Version(2, 0), - }; + HttpClient = new HttpClient(CreateHandler(), true) { + Timeout = System.Threading.Timeout.InfiniteTimeSpan, + DefaultRequestVersion = new Version(2, 0) + }, #else - grpcChannelOptions.HttpHandler = httpMessageHandler; + HttpHandler = CreateHandler(), #endif + LoggerFactory = settings.LoggerFactory, + Credentials = settings.ChannelCredentials, + DisposeHttpClient = true, + MaxReceiveMessageSize = MaxReceiveMessageLength + }); + + HttpMessageHandler CreateHandler() { + if (settings.CreateHttpMessageHandler != null) { + return settings.CreateHttpMessageHandler.Invoke(); + } +#if NET + var handler = new SocketsHttpHandler { + KeepAlivePingDelay = settings.ConnectivitySettings.KeepAliveInterval, + KeepAlivePingTimeout = settings.ConnectivitySettings.KeepAliveTimeout, + EnableMultipleHttp2Connections = true, + }; - return TChannel.ForAddress( - address, - grpcChannelOptions - ); + if (!settings.ConnectivitySettings.TlsVerifyCert) { + handler.SslOptions.RemoteCertificateValidationCallback = delegate { return true; }; + } +#else + var handler = new WinHttpHandler { + TcpKeepAliveEnabled = true, + TcpKeepAliveTime = settings.ConnectivitySettings.KeepAliveTimeout, + TcpKeepAliveInterval = settings.ConnectivitySettings.KeepAliveInterval, + EnableMultipleHttp2Connections = true + }; + + if (!settings.ConnectivitySettings.TlsVerifyCert) { + handler.ServerCertificateValidationCallback = delegate { return true; }; + } +#endif + return handler; + } } } } From 5dbe63ee59f816a4d6f4f7ccc62c1a2b6342a067 Mon Sep 17 00:00:00 2001 From: William Chong Date: Thu, 25 Jan 2024 19:49:34 +0400 Subject: [PATCH 37/41] Explcitly map WinHttpHandler error messages during subscription handling --- .../PersistentSubscription.cs | 52 ++++++++++++------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/src/EventStore.Client.PersistentSubscriptions/PersistentSubscription.cs b/src/EventStore.Client.PersistentSubscriptions/PersistentSubscription.cs index 0047e1c73..6f1531dcf 100644 --- a/src/EventStore.Client.PersistentSubscriptions/PersistentSubscription.cs +++ b/src/EventStore.Client.PersistentSubscriptions/PersistentSubscription.cs @@ -13,11 +13,11 @@ namespace EventStore.Client { /// public class PersistentSubscription : IDisposable { private readonly Func _eventAppeared; - private readonly Action _subscriptionDropped; - private readonly IDisposable _disposable; - private readonly CancellationToken _cancellationToken; - private readonly AsyncDuplexStreamingCall _call; - private readonly ILogger _log; + private readonly Action _subscriptionDropped; + private readonly IDisposable _disposable; + private readonly CancellationToken _cancellationToken; + private readonly AsyncDuplexStreamingCall _call; + private readonly ILogger _log; private int _subscriptionDroppedInvoked; @@ -60,13 +60,13 @@ private PersistentSubscription( Action subscriptionDropped, CancellationToken cancellationToken, IDisposable disposable) { - _call = call; - _eventAppeared = eventAppeared; + _call = call; + _eventAppeared = eventAppeared; _subscriptionDropped = subscriptionDropped; - _cancellationToken = cancellationToken; - _disposable = disposable; - _log = log; - SubscriptionId = call.ResponseStream.Current.SubscriptionConfirmation.SubscriptionId; + _cancellationToken = cancellationToken; + _disposable = disposable; + _log = log; + SubscriptionId = call.ResponseStream.Current.SubscriptionConfirmation.SubscriptionId; Task.Run(Subscribe); } @@ -130,7 +130,7 @@ public Task Nack(PersistentSubscriptionNakEventAction action, string reason, par /// The s to nak. There should not be more than 2000 to nak at a time. /// The number of resolvedEvents exceeded the limit of 2000. public Task Nack(PersistentSubscriptionNakEventAction action, string reason, - params ResolvedEvent[] resolvedEvents) => + params ResolvedEvent[] resolvedEvents) => Nack(action, reason, Array.ConvertAll(resolvedEvents, resolvedEvent => resolvedEvent.OriginalEvent.EventId)); @@ -186,6 +186,22 @@ await _eventAppeared( } } catch (Exception ex) { if (_subscriptionDroppedInvoked == 0) { +#if NET48 + // The gRPC client for .NET 48 uses WinHttpHandler under the hood for sending HTTP requests. + // In certain scenarios, this can lead to exceptions of type WinHttpException being thrown. + // One such scenario is when the server abruptly closes the connection, which results in a WinHttpException with the error code 12030. + // Additionally, there are cases where the server response does not include the 'grpc-status' header. + // The absence of this header leads to an RpcException with the status code 'Cancelled' and the message "No grpc-status found on response". + // The switch statement below handles these specific exceptions and translates them into a PersistentSubscriptionDroppedByServerException + // which is what our tests expect. + switch (ex) { + case RpcException { StatusCode: StatusCode.Unavailable } rex1 when rex1.Message.Contains("WinHttpException: Error 12030"): + case RpcException { StatusCode: StatusCode.Cancelled } rex2 when rex2.Message.Contains("No grpc-status found on response"): + ex = new PersistentSubscriptionDroppedByServerException("", "", ex); + break; + } +#endif + _log.LogError(ex, "Persistent Subscription {subscriptionId} was dropped because an error occurred on the server.", SubscriptionId); @@ -205,7 +221,7 @@ await _eventAppeared( ConvertToEventRecord(response.Event.Link), response.Event.PositionCase switch { ReadResp.Types.ReadEvent.PositionOneofCase.CommitPosition => response.Event.CommitPosition, - _ => null + _ => null }); EventRecord? ConvertToEventRecord(ReadResp.Types.ReadEvent.Types.RecordedEvent? e) => @@ -250,14 +266,14 @@ private Task NackInternal(Uuid[] ids, PersistentSubscriptionNakEventAction actio Array.ConvertAll(ids, id => id.ToDto()) }, Action = action switch { - PersistentSubscriptionNakEventAction.Park => ReadReq.Types.Nack.Types.Action.Park, + PersistentSubscriptionNakEventAction.Park => ReadReq.Types.Nack.Types.Action.Park, PersistentSubscriptionNakEventAction.Retry => ReadReq.Types.Nack.Types.Action.Retry, - PersistentSubscriptionNakEventAction.Skip => ReadReq.Types.Nack.Types.Action.Skip, - PersistentSubscriptionNakEventAction.Stop => ReadReq.Types.Nack.Types.Action.Stop, - _ => ReadReq.Types.Nack.Types.Action.Unknown + PersistentSubscriptionNakEventAction.Skip => ReadReq.Types.Nack.Types.Action.Skip, + PersistentSubscriptionNakEventAction.Stop => ReadReq.Types.Nack.Types.Action.Stop, + _ => ReadReq.Types.Nack.Types.Action.Unknown }, Reason = reason } }); } -} +} \ No newline at end of file From 798d083cfe9eb65873b623389220263e718ff93b Mon Sep 17 00:00:00 2001 From: William Chong Date: Thu, 25 Jan 2024 19:57:40 +0400 Subject: [PATCH 38/41] Fix unnecessary break and improve WinHttpHandler comment --- .../PersistentSubscription.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/EventStore.Client.PersistentSubscriptions/PersistentSubscription.cs b/src/EventStore.Client.PersistentSubscriptions/PersistentSubscription.cs index 6f1531dcf..6db2a53e3 100644 --- a/src/EventStore.Client.PersistentSubscriptions/PersistentSubscription.cs +++ b/src/EventStore.Client.PersistentSubscriptions/PersistentSubscription.cs @@ -193,15 +193,14 @@ await _eventAppeared( // Additionally, there are cases where the server response does not include the 'grpc-status' header. // The absence of this header leads to an RpcException with the status code 'Cancelled' and the message "No grpc-status found on response". // The switch statement below handles these specific exceptions and translates them into a PersistentSubscriptionDroppedByServerException - // which is what our tests expect. - switch (ex) { - case RpcException { StatusCode: StatusCode.Unavailable } rex1 when rex1.Message.Contains("WinHttpException: Error 12030"): - case RpcException { StatusCode: StatusCode.Cancelled } rex2 when rex2.Message.Contains("No grpc-status found on response"): - ex = new PersistentSubscriptionDroppedByServerException("", "", ex); - break; + // which is what our tests expect. The downside of this approach is that it does not return the stream name and group name. + if (ex is RpcException { StatusCode: StatusCode.Unavailable } rex1 && rex1.Status.Detail.Contains("WinHttpException: Error 12030")) { + ex = new PersistentSubscriptionDroppedByServerException("", "", ex); + } else if (ex is RpcException { StatusCode: StatusCode.Cancelled } rex2 + && rex2.Status.Detail.Contains("No grpc-status found on response")) { + ex = new PersistentSubscriptionDroppedByServerException("", "", ex); } #endif - _log.LogError(ex, "Persistent Subscription {subscriptionId} was dropped because an error occurred on the server.", SubscriptionId); From fbc1fe6ac80c9f6c8271c5e322382cadb4759d10 Mon Sep 17 00:00:00 2001 From: William Chong Date: Thu, 25 Jan 2024 21:07:05 +0400 Subject: [PATCH 39/41] Increase timeout and reconnect delay in subscription test --- .../PersistentSubscription.cs | 25 ++++++++++--------- .../deleting_existing_with_subscriber.cs | 3 +++ .../update_existing_with_subscribers.cs | 3 +++ .../deleting_existing_with_subscriber.cs | 3 +++ .../update_existing_with_subscribers.cs | 3 +++ .../Subscriptions/reconnection.cs | 2 +- 6 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/EventStore.Client.PersistentSubscriptions/PersistentSubscription.cs b/src/EventStore.Client.PersistentSubscriptions/PersistentSubscription.cs index 6db2a53e3..901f9c36c 100644 --- a/src/EventStore.Client.PersistentSubscriptions/PersistentSubscription.cs +++ b/src/EventStore.Client.PersistentSubscriptions/PersistentSubscription.cs @@ -187,18 +187,19 @@ await _eventAppeared( } catch (Exception ex) { if (_subscriptionDroppedInvoked == 0) { #if NET48 - // The gRPC client for .NET 48 uses WinHttpHandler under the hood for sending HTTP requests. - // In certain scenarios, this can lead to exceptions of type WinHttpException being thrown. - // One such scenario is when the server abruptly closes the connection, which results in a WinHttpException with the error code 12030. - // Additionally, there are cases where the server response does not include the 'grpc-status' header. - // The absence of this header leads to an RpcException with the status code 'Cancelled' and the message "No grpc-status found on response". - // The switch statement below handles these specific exceptions and translates them into a PersistentSubscriptionDroppedByServerException - // which is what our tests expect. The downside of this approach is that it does not return the stream name and group name. - if (ex is RpcException { StatusCode: StatusCode.Unavailable } rex1 && rex1.Status.Detail.Contains("WinHttpException: Error 12030")) { - ex = new PersistentSubscriptionDroppedByServerException("", "", ex); - } else if (ex is RpcException { StatusCode: StatusCode.Cancelled } rex2 - && rex2.Status.Detail.Contains("No grpc-status found on response")) { - ex = new PersistentSubscriptionDroppedByServerException("", "", ex); + switch (ex) { + // The gRPC client for .NET 48 uses WinHttpHandler under the hood for sending HTTP requests. + // In certain scenarios, this can lead to exceptions of type WinHttpException being thrown. + // One such scenario is when the server abruptly closes the connection, which results in a WinHttpException with the error code 12030. + // Additionally, there are cases where the server response does not include the 'grpc-status' header. + // The absence of this header leads to an RpcException with the status code 'Cancelled' and the message "No grpc-status found on response". + // The switch statement below handles these specific exceptions and translates them into a PersistentSubscriptionDroppedByServerException + // which is what our tests expect. The downside of this approach is that it does not return the stream name and group name. + case RpcException { StatusCode: StatusCode.Unavailable } rex1 when rex1.Status.Detail.Contains("WinHttpException: Error 12030"): + case RpcException { StatusCode: StatusCode.Cancelled } rex2 + when rex2.Status.Detail.Contains("No grpc-status found on response"): + ex = new PersistentSubscriptionDroppedByServerException("", "", ex); + break; } #endif _log.LogError(ex, diff --git a/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/deleting_existing_with_subscriber.cs b/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/deleting_existing_with_subscriber.cs index 4f7f2646d..4403087e6 100644 --- a/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/deleting_existing_with_subscriber.cs +++ b/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/deleting_existing_with_subscriber.cs @@ -11,8 +11,11 @@ public async Task the_subscription_is_dropped() { var (reason, exception) = await _fixture.Dropped.WithTimeout(); Assert.Equal(SubscriptionDroppedReason.ServerError, reason); var ex = Assert.IsType(exception); + +#if NET Assert.Equal(SystemStreams.AllStream, ex.StreamName); Assert.Equal("groupname123", ex.GroupName); +#endif } [Fact(Skip = "Isn't this how it should work?")] diff --git a/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/update_existing_with_subscribers.cs b/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/update_existing_with_subscribers.cs index 6113954c0..82c0ec320 100644 --- a/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/update_existing_with_subscribers.cs +++ b/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/update_existing_with_subscribers.cs @@ -13,8 +13,11 @@ public async Task existing_subscriptions_are_dropped() { var (reason, exception) = await _fixture.Dropped.WithTimeout(TimeSpan.FromSeconds(10)); Assert.Equal(SubscriptionDroppedReason.ServerError, reason); var ex = Assert.IsType(exception); + +#if NET Assert.Equal(SystemStreams.AllStream, ex.StreamName); Assert.Equal(Group, ex.GroupName); +#endif } public class Fixture : EventStoreClientFixture { diff --git a/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToStream/deleting_existing_with_subscriber.cs b/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToStream/deleting_existing_with_subscriber.cs index 8084881da..cb268b603 100644 --- a/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToStream/deleting_existing_with_subscriber.cs +++ b/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToStream/deleting_existing_with_subscriber.cs @@ -12,8 +12,11 @@ public async Task the_subscription_is_dropped() { var (reason, exception) = await _fixture.Dropped.WithTimeout(); Assert.Equal(SubscriptionDroppedReason.ServerError, reason); var ex = Assert.IsType(exception); + +#if NET Assert.Equal(Stream, ex.StreamName); Assert.Equal("groupname123", ex.GroupName); +#endif } [Fact(Skip = "Isn't this how it should work?")] diff --git a/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToStream/update_existing_with_subscribers.cs b/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToStream/update_existing_with_subscribers.cs index e0712a27d..72b59b778 100644 --- a/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToStream/update_existing_with_subscribers.cs +++ b/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToStream/update_existing_with_subscribers.cs @@ -13,8 +13,11 @@ public async Task existing_subscriptions_are_dropped() { var (reason, exception) = await _fixture.Dropped.WithTimeout(TimeSpan.FromSeconds(10)); Assert.Equal(SubscriptionDroppedReason.ServerError, reason); var ex = Assert.IsType(exception); + +#if NET Assert.Equal(Stream, ex.StreamName); Assert.Equal(Group, ex.GroupName); +#endif } public class Fixture : EventStoreClientFixture { diff --git a/test/EventStore.Client.Streams.Tests/Subscriptions/reconnection.cs b/test/EventStore.Client.Streams.Tests/Subscriptions/reconnection.cs index df9162130..db9601d4f 100644 --- a/test/EventStore.Client.Streams.Tests/Subscriptions/reconnection.cs +++ b/test/EventStore.Client.Streams.Tests/Subscriptions/reconnection.cs @@ -6,7 +6,7 @@ namespace EventStore.Client.Streams.Tests.Subscriptions; [Trait("Category", "Subscriptions")] public class @reconnection(ITestOutputHelper output, ReconnectionFixture fixture) : EventStoreTests(output, fixture) { [Theory] - [InlineData(4, 1000, 0, 15000)] + [InlineData(4, 5000, 0, 30000)] public async Task when_the_connection_is_lost(int expectedNumberOfEvents, int reconnectDelayMs, int serviceRestartDelayMs, int testTimeoutMs) { using var cancellator = new CancellationTokenSource().With(x => x.CancelAfter(testTimeoutMs)); From de28e9d5cc2d449b452a6347fd7ece6fa97ba797 Mon Sep 17 00:00:00 2001 From: William Chong Date: Fri, 26 Jan 2024 17:37:59 +0400 Subject: [PATCH 40/41] Improve comment --- .../PersistentSubscription.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/EventStore.Client.PersistentSubscriptions/PersistentSubscription.cs b/src/EventStore.Client.PersistentSubscriptions/PersistentSubscription.cs index 901f9c36c..56b8d305d 100644 --- a/src/EventStore.Client.PersistentSubscriptions/PersistentSubscription.cs +++ b/src/EventStore.Client.PersistentSubscriptions/PersistentSubscription.cs @@ -193,8 +193,9 @@ await _eventAppeared( // One such scenario is when the server abruptly closes the connection, which results in a WinHttpException with the error code 12030. // Additionally, there are cases where the server response does not include the 'grpc-status' header. // The absence of this header leads to an RpcException with the status code 'Cancelled' and the message "No grpc-status found on response". - // The switch statement below handles these specific exceptions and translates them into a PersistentSubscriptionDroppedByServerException - // which is what our tests expect. The downside of this approach is that it does not return the stream name and group name. + // The switch statement below handles these specific exceptions and translates them into the appropriate + // PersistentSubscriptionDroppedByServerException exception. The downside of this approach is that it does not return the stream name + // and group name. case RpcException { StatusCode: StatusCode.Unavailable } rex1 when rex1.Status.Detail.Contains("WinHttpException: Error 12030"): case RpcException { StatusCode: StatusCode.Cancelled } rex2 when rex2.Status.Detail.Contains("No grpc-status found on response"): From ec9b00c9d7751c8a10e4c968f0aa903548de6ab8 Mon Sep 17 00:00:00 2001 From: josephcummings Date: Mon, 29 Jan 2024 14:37:32 +0000 Subject: [PATCH 41/41] Rm usage of redundant db config option 'ENABLE_EXTERNAL_TCP' breaking CI --- .../Fixtures/EventStoreTestCluster.cs | 1 - .../Fixtures/EventStoreTestNode.cs | 1 - test/EventStore.Client.Tests.Common/shared.env | 1 - 3 files changed, 3 deletions(-) diff --git a/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestCluster.cs b/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestCluster.cs index ff3ec644b..ad8246843 100644 --- a/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestCluster.cs +++ b/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestCluster.cs @@ -24,7 +24,6 @@ public static EventStoreFixtureOptions DefaultOptions() { ["EVENTSTORE_INT_TCP_PORT"] = "1112", ["EVENTSTORE_HTTP_PORT"] = "2113", ["EVENTSTORE_DISCOVER_VIA_DNS"] = "false", - ["EVENTSTORE_ENABLE_EXTERNAL_TCP"] = "false", ["EVENTSTORE_STREAM_EXISTENCE_FILTER_SIZE"] = "10000", ["EVENTSTORE_STREAM_INFO_CACHE_CAPACITY"] = "10000" }; diff --git a/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestNode.cs b/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestNode.cs index 97dc3725e..e767b3bae 100644 --- a/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestNode.cs +++ b/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestNode.cs @@ -32,7 +32,6 @@ public static EventStoreFixtureOptions DefaultOptions() { .With(x => x.ConnectivitySettings.DiscoveryInterval = FromSeconds(1)); var defaultEnvironment = new Dictionary(GlobalEnvironment.Variables) { - ["EVENTSTORE_ENABLE_EXTERNAL_TCP"] = "false", ["EVENTSTORE_MEM_DB"] = "true", ["EVENTSTORE_CHUNK_SIZE"] = (1024 * 1024).ToString(), ["EVENTSTORE_CERTIFICATE_FILE"] = "/etc/eventstore/certs/node/node.crt", diff --git a/test/EventStore.Client.Tests.Common/shared.env b/test/EventStore.Client.Tests.Common/shared.env index 2b8c93eff..f753bc9c7 100644 --- a/test/EventStore.Client.Tests.Common/shared.env +++ b/test/EventStore.Client.Tests.Common/shared.env @@ -3,7 +3,6 @@ EVENTSTORE_INT_TCP_PORT=1112 EVENTSTORE_HTTP_PORT=2113 EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH=/etc/eventstore/certs/ca EVENTSTORE_DISCOVER_VIA_DNS=false -EVENTSTORE_ENABLE_EXTERNAL_TCP=false EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP=false EVENTSTORE_STREAM_EXISTENCE_FILTER_SIZE=10000