Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Fix failing sync tests #3096

Merged
merged 32 commits into from
Jan 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
f4f4f1f
Run tests for iOS and Linux; increase timeout
nirinchev Nov 16, 2022
76dcef0
Dont cleanup cluster
nirinchev Nov 16, 2022
1263868
Increase some timeouts
nirinchev Nov 16, 2022
225698d
add some timeouts and more verbose error messages
nirinchev Nov 17, 2022
beb71ce
Bump to M20, more tests
nirinchev Nov 17, 2022
55f07d6
Increase timeout
nirinchev Nov 17, 2022
cad926e
Add more timeouts and logs
nirinchev Nov 17, 2022
16a892b
Try to initialize asymmetric tables
nirinchev Nov 17, 2022
2b062a0
Fix order of table initialization
nirinchev Nov 21, 2022
6697d9a
Remove asym table initialization
nirinchev Nov 21, 2022
8965a14
Don't use range for substring
nirinchev Nov 21, 2022
4de48f2
Cleanup some session tests
nirinchev Nov 21, 2022
23820a7
A bit more cleanup
nirinchev Nov 21, 2022
cdf5d01
Merge branch 'main' into ni/sync-tests
nirinchev Nov 22, 2022
ed90bfa
Disable some tests, add a 1s delay
nirinchev Nov 23, 2022
9cf7206
Merge branch 'main' into ni/sync-tests
nirinchev Nov 25, 2022
54d8276
Fix some warnings and some tests
nirinchev Nov 25, 2022
3a3d8af
Rework timeouts, add token refresh logic for baas client
nirinchev Nov 28, 2022
0833f03
Bump timeouts
nirinchev Nov 28, 2022
5b97433
Try to log test output to file
nirinchev Nov 28, 2022
12b0039
Don't use .net6 api
nirinchev Nov 28, 2022
4a388e9
Don't use Enum.Parse<T>
nirinchev Nov 28, 2022
7b59e74
Remove wrong if condition
nirinchev Nov 30, 2022
a280928
Try to work around server race condition
nirinchev Dec 1, 2022
ca3d70f
Merge branch 'main' into ni/sync-tests
nirinchev Dec 16, 2022
8d65a02
Merge branch 'main' into ni/sync-tests
nirinchev Jan 10, 2023
9d2579c
Cleanup some leftovers
nirinchev Jan 10, 2023
cfd54b6
Make DeleteWithRetries sync again
nirinchev Jan 11, 2023
5640642
Merge branch 'main' into ni/sync-tests
nirinchev Jan 12, 2023
e732b56
Don't create a stringbuilder in a loop
nirinchev Jan 12, 2023
6aae0aa
Remove assertion
nirinchev Jan 16, 2023
4870a4e
Validate args in BaasClient
nirinchev Jan 16, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/templates/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,4 @@ jobs:
uses: ./.github/workflows/test-weaver.yml
name: Test
_: #@ template.replace(runTests("Code Coverage"))
_: #@ template.replace(cleanupBaas(["Code Coverage"]))
_: #@ template.replace(cleanupBaas(["Code Coverage"]))
2 changes: 1 addition & 1 deletion .github/templates/test-code-coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
run-tests:
runs-on: ubuntu-latest
name: Code Coverage
timeout-minutes: 45
timeout-minutes: 90
steps:
- #@ setupDotnet()
- #@ template.replace(prepareTest(fetchWrappers=True))
Expand Down
2 changes: 1 addition & 1 deletion .github/templates/test-ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
test-xamarin:
runs-on: macos-latest
name: Xamarin.iOS
timeout-minutes: 45
timeout-minutes: 90
steps:
- #@ template.replace(prepareTest())
- #@ template.replace(buildTests("Tests/Tests.iOS", Platform="iPhoneSimulator"))
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/deploy-baas.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
atlasUrl: ${{ inputs.AtlasBaseUrl}}
apiKey: ${{ secrets.AtlasPublicKey}}
privateApiKey: ${{ secrets.AtlasPrivateKey }}
clusterSize: M10
clusterSize: M20
deploy-apps:
name: Deploy Apps
needs: deploy-baas
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-code-coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
run-tests:
runs-on: ubuntu-latest
name: Code Coverage
timeout-minutes: 45
timeout-minutes: 90
steps:
- name: Configure .NET
uses: actions/setup-dotnet@607fce577a46308457984d59e4954e075820f10a
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
test-xamarin:
runs-on: macos-latest
name: Xamarin.iOS
timeout-minutes: 45
timeout-minutes: 90
steps:
- name: Checkout code
uses: actions/checkout@v3
Expand Down
28 changes: 20 additions & 8 deletions Realm/Realm/Extensions/TaskExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,31 @@

internal static class TaskExtensions
{
public static Task<T> Timeout<T>(this Task<T> task, int millisecondTimeout)
public static Task<T> Timeout<T>(this Task<T> task, int millisecondTimeout, string detail = null)
{
return Task.WhenAny(task, Task.Delay(millisecondTimeout)).ContinueWith(t =>
{
if (t.Result == task)
if (t.Result != task)
{
if (task.IsFaulted)
var errorMessage = $"The operation has timed out after {millisecondTimeout} ms.";
if (detail != null)
{
throw task.Exception.InnerException;
errorMessage += $" {detail}";
}

return task.Result;
throw new TimeoutException(errorMessage);
}

throw new TimeoutException($"The operation has timed out after {millisecondTimeout} ms.");
if (task.IsFaulted)
{
throw task.Exception.InnerException;
}

return task.Result;
});
}

public static async Task Timeout(this Task task, int millisecondTimeout, Task errorTask = null)
public static async Task Timeout(this Task task, int millisecondTimeout, Task errorTask = null, string detail = null)
{
var tasks = new List<Task> { task, Task.Delay(millisecondTimeout) };
if (errorTask != null)
Expand All @@ -51,7 +57,13 @@ public static async Task Timeout(this Task task, int millisecondTimeout, Task er
var completed = await Task.WhenAny(tasks);
if (completed != task && completed != errorTask)
{
throw new TimeoutException($"The operation has timed out after {millisecondTimeout} ms.");
var errorMessage = $"The operation has timed out after {millisecondTimeout} ms.";
if (detail != null)
{
errorMessage += $" {detail}";
}

throw new TimeoutException(errorMessage);
}

await completed;
Expand Down
6 changes: 5 additions & 1 deletion Realm/Realm/Handles/SubscriptionSetHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,10 @@ public async Task<SubscriptionSetState> WaitForStateChangeAsync()

return await tcs.Task;
}
catch (Exception ex) when (ex.Message == "Active SubscriptionSet without a SubscriptionStore")
{
throw new TaskCanceledException("The SubscriptionSet was closed before the wait could complete. This is likely because the Realm it belongs to was disposed.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would make sense to move this message to core?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is somewhat c#-specific message, so I don't think the Core team would like to adopt it.

}
finally
{
tcsHandle.Free();
Expand Down Expand Up @@ -327,7 +331,7 @@ private static void HandleStateWaitCallback(IntPtr taskCompletionSource, Subscri
tcs.TrySetResult(state);
break;
case RealmValueType.Int when message.AsInt() == -1:
tcs.TrySetCanceled();
tcs.TrySetException(new TaskCanceledException("The SubscriptionSet was closed before the wait could complete. This is likely because the Realm it belongs to was disposed."));
break;
default:
tcs.TrySetException(new SubscriptionException(message.AsString()));
Expand Down
65 changes: 63 additions & 2 deletions Realm/Realm/Logging/Logger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@
////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Realms.Sync;

namespace Realms.Logging
Expand Down Expand Up @@ -165,7 +168,7 @@ protected override void LogImpl(LogLevel level, string message)

private class FileLogger : Logger
{
private readonly object locker = new object();
private readonly object locker = new();
private readonly string _filePath;
private readonly Encoding _encoding;

Expand Down Expand Up @@ -205,7 +208,7 @@ protected override void LogImpl(LogLevel level, string message)

internal class InMemoryLogger : Logger
{
private readonly StringBuilder _builder = new StringBuilder();
private readonly StringBuilder _builder = new();

protected override void LogImpl(LogLevel level, string message)
{
Expand All @@ -225,5 +228,63 @@ public string GetLog()

public void Clear() => _builder.Clear();
}

internal class AsyncFileLogger : Logger, IDisposable
{
private readonly ConcurrentQueue<string> _queue = new();
private readonly string _filePath;
private readonly Encoding _encoding;
private readonly AutoResetEvent _hasNewItems = new(false);
private readonly AutoResetEvent _flush = new(false);
private readonly Task _runner;
private volatile bool _isFlushing;

public AsyncFileLogger(string filePath, Encoding encoding = null)
{
_filePath = filePath;
_encoding = encoding ?? Encoding.UTF8;
_runner = Task.Run(Run);
}

public void Dispose()
{
_isFlushing = true;
_flush.Set();
_runner.Wait();

_hasNewItems.Dispose();
_flush.Dispose();
}

protected override void LogImpl(LogLevel level, string message)
{
if (!_isFlushing)
{
_queue.Enqueue(FormatLog(level, message));
_hasNewItems.Set();
}
}

private void Run()
{
var sb = new StringBuilder();
while (true)
{
sb.Clear();
WaitHandle.WaitAny(new[] { _hasNewItems, _flush });
while (_queue.TryDequeue(out var item))
{
sb.AppendLine(item);
}

System.IO.File.AppendAllText(_filePath, sb.ToString(), _encoding);

if (_isFlushing)
{
return;
}
}
}
}
}
}
10 changes: 3 additions & 7 deletions Realm/Realm/Realm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -429,11 +429,7 @@ public void Dispose()
if (!IsClosed)
{
_activeTransaction?.Dispose();

if (SharedRealmHandle.OwnsNativeRealm)
{
_state.RemoveRealm(this);
}
_state.RemoveRealm(this, closeOnEmpty: SharedRealmHandle.OwnsNativeRealm);
Comment on lines -433 to +432
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes the behavior to always remove the Realm from the state, even when it's unowned. The reason is that otherwise we ended up not closing the Realm in certain situations. Example:

var config = new RealmConfiguration
{
    PopulateInitialData = (r) =>
    {
		// (1)
    }
}
using var realm = Realm.GetInstance(config);

// (2)

At point (1), we create an unowned Realm instance - r. When created, it will create a RealmState and set it to the native instance. At point (2), we get a regular Realm instance, that points to the same native Realm as r. This means that the state now references two Realms - r and realm. When we dispose realm, we remove it from the state, but we don't close the native realm because we see that the _weakRealms collection is not empty.

With this change, we remove unowned realms from the state, but don't explicitly call close on the native Realm, because we don't own it (yet). When an owned instance is created for the native Realm, we're free to close it, because there won't be unowned managed instances that keep it around.


_state = null;
SharedRealmHandle.Close(); // Note: this closes the *handle*, it does not trigger realm::Realm::close().
Expand Down Expand Up @@ -1708,14 +1704,14 @@ public void AddRealm(Realm realm)
/// 4. Once the last instance is deleted, the CSharpBindingContext destructor is called, which frees the state GCHandle.
/// 5. The State is now eligible for collection, and its fields will be GC-ed.
/// </summary>
public void RemoveRealm(Realm realm)
public void RemoveRealm(Realm realm, bool closeOnEmpty)
{
_weakRealms.RemoveAll(r =>
{
return !r.TryGetTarget(out var other) || ReferenceEquals(realm, other);
});

if (!_weakRealms.Any())
if (closeOnEmpty && !_weakRealms.Any())
{
realm.SharedRealmHandle.CloseRealm();
}
Expand Down
2 changes: 1 addition & 1 deletion Tests/Realm.Tests/Database/InstanceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,7 @@ public void GetInstanceAsync_ExecutesMigrationsInBackground()
var sw = new Stopwatch();
sw.Start();

using var realm = await GetRealmAsync(config).Timeout(1000);
using var realm = await GetRealmAsync(config, 1000);

sw.Stop();

Expand Down
9 changes: 3 additions & 6 deletions Tests/Realm.Tests/Database/NotificationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,11 @@ public void ShouldTriggerRealmChangedEvent()
[Test]
public void RealmError_WhenNoSubscribers_OutputsMessageInConsole()
{
using var sw = new StringWriter();
var original = Logger.Default;

Logger.Default = Logger.Function(sw.WriteLine);
var logger = new Logger.InMemoryLogger();
Logger.Default = logger;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to reset the original logger?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is done automatically in RealmTest.TearDown

_realm.NotifyError(new Exception());

Assert.That(sw.ToString(), Does.Contain("exception").And.Contains("Realm.Error"));
Logger.Default = original;
Assert.That(logger.GetLog(), Does.Contain("exception").And.Contains("Realm.Error"));
}

[Test]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.IO;
using System.Linq;
Expand All @@ -41,6 +40,8 @@ public partial class ObjectWithPartitionValue : IRealmObject, INotifyPropertyCha
Realms.Schema.Property.Primitive("Guid", Realms.RealmValueType.Guid, isPrimaryKey: false, isIndexed: false, isNullable: false, managedName: "Guid"),
}.Build();

private ObjectWithPartitionValue() {}

#region IRealmObject implementation

private IObjectWithPartitionValueAccessor _accessor;
Expand Down Expand Up @@ -283,7 +284,7 @@ internal class ObjectWithPartitionValueUnmanagedAccessor : Realms.UnmanagedAcces
{
public override ObjectSchema ObjectSchema => ObjectWithPartitionValue.RealmSchema;

private string _id;
private string _id = Guid.NewGuid().ToString();
public string Id
{
get => _id;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.IO;
using System.Linq;
Expand Down
8 changes: 6 additions & 2 deletions Tests/Realm.Tests/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,20 @@ public static int Main(string[] args)
Console.WriteLine($"Running on {RuntimeInformation.OSDescription} / CPU {RuntimeInformation.ProcessArchitecture} / Framework {RuntimeInformation.FrameworkDescription}");

var autorun = new AutoRun(typeof(Program).GetTypeInfo().Assembly);
var arguments = Sync.SyncTestHelpers.ExtractBaasSettings(args);
IDisposable logger = null;
(args, logger) = Sync.SyncTestHelpers.SetLoggerFromArgs(args);
args = Sync.SyncTestHelpers.ExtractBaasSettings(args);

autorun.Execute(arguments);
autorun.Execute(args);

var resultPath = args.FirstOrDefault(a => a.StartsWith("--result="))?.Replace("--result=", string.Empty);
if (!string.IsNullOrEmpty(resultPath))
{
TestHelpers.TransformTestResults(resultPath);
}

logger?.Dispose();

return 0;
}
}
Expand Down
Loading