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

1.19.14 dotnet test crashes when running multiple tests #1064

Closed
groogiam opened this issue May 9, 2023 · 43 comments · Fixed by #1132 or #1136
Closed

1.19.14 dotnet test crashes when running multiple tests #1064

groogiam opened this issue May 9, 2023 · 43 comments · Fixed by #1132 or #1136
Assignees
Labels
investigate This issue require further investigation before closing.

Comments

@groogiam
Copy link

groogiam commented May 9, 2023

Describe the bug

After updating my nuget package reference to 1.19.14 dotnet test crashes with a stack overflow exception when running multiple tests.

This appears to happen after rendering when waiting for state. E.g.

        cut.WaitForState(() => cut.Instance.EntityPermission != null);

Results in this exception


Stack overflow.
   at System.Collections.Generic.Dictionary`2[[System.Int32, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[Microsoft.AspNetCore.Components.RenderTree.ArrayRange`1[[Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame, Microsoft.AspNetCore.Components, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]], Microsoft.AspNetCore.Components, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].GetBucket(UInt32)
   at System.Collections.Generic.Dictionary`2[[System.Int32, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[Microsoft.AspNetCore.Components.RenderTree.ArrayRange`1[[Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame, Microsoft.AspNetCore.Components, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]], Microsoft.AspNetCore.Components, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].FindValue(Int32)
   at System.Collections.Generic.Dictionary`2[[System.Int32, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[Microsoft.AspNetCore.Components.RenderTree.ArrayRange`1[[Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame, Microsoft.AspNetCore.Components, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]], Microsoft.AspNetCore.Components, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].ContainsKey(Int32)
   at Bunit.Rendering.RenderTreeFrameDictionary.Contains(Int32)
   at Bunit.Rendering.TestRenderer.GetOrLoadRenderTreeFrame(Bunit.Rendering.RenderTreeFrameDictionary, Int32)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
...
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.LoadRenderTreeFrames(Int32, Bunit.Rendering.RenderTreeFrameDictionary)
   at Bunit.Rendering.TestRenderer.UpdateDisplay(Microsoft.AspNetCore.Components.RenderTree.RenderBatch ByRef)
   at Bunit.Rendering.TestRenderer+<>c.<UpdateDisplayAsync>b__27_0(System.Object)
   at Xunit.Sdk.AsyncTestSyncContext.Send(System.Threading.SendOrPostCallback, System.Object)
   at Bunit.Rendering.TestRenderer.UpdateDisplayAsync(Microsoft.AspNetCore.Components.RenderTree.RenderBatch ByRef)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()
   at Bunit.Rendering.TestRenderer.ProcessPendingRender()
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToRenderQueue(Int32, Microsoft.AspNetCore.Components.RenderFragment)
   at Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged()
   at Microsoft.AspNetCore.Components.ComponentBase.Microsoft.AspNetCore.Components.IHandleEvent.HandleEventAsync(Microsoft.AspNetCore.Components.EventCallbackWorkItem, System.Object)
   at AppPotion.Web.Ui.Shared.EntityEditor.EditorActionBarComponent+<ActionButtonClicked>d__23.MoveNext()
   at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.__Canon, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext(System.Threading.Thread)
   at System.Runtime.CompilerServices.TaskAwaiter+<>c.<OutputWaitEtwEvents>b__12_0(System.Action, System.Threading.Tasks.Task)
   at System.Threading.Tasks.AwaitTaskContinuation.RunCallback(System.Threading.ContextCallback, System.Object, System.Threading.Tasks.Task ByRef)
   at System.Threading.Tasks.Task.RunContinuations(System.Object)
   at System.Threading.Tasks.Task`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].TrySetResult(System.Threading.Tasks.VoidTaskResult)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].SetExistingTaskResult(System.Threading.Tasks.Task`1<System.Threading.Tasks.VoidTaskResult>, System.Threading.Tasks.VoidTaskResult)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult()
   at AppPotion.Web.Ui.Framework.EntityEditor.EntityActionDescriptor+<ExecuteAsync>d__43.MoveNext()
   at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.__Canon, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext(System.Threading.Thread)
   at System.Runtime.CompilerServices.TaskAwaiter+<>c.<OutputWaitEtwEvents>b__12_0(System.Action, System.Threading.Tasks.Task)
   at System.Threading.Tasks.AwaitTaskContinuation.RunCallback(System.Threading.ContextCallback, System.Object, System.Threading.Tasks.Task ByRef)
   at System.Threading.Tasks.Task.RunContinuations(System.Object)
   at System.Threading.Tasks.Task`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].TrySetResult(System.Threading.Tasks.VoidTaskResult)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].SetExistingTaskResult(System.Threading.Tasks.Task`1<System.Threading.Tasks.VoidTaskResult>, System.Threading.Tasks.VoidTaskResult)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult()
   at AppPotion.Web.Ui.Shared.EntityEditor.EntityEditorComponentBase`1+<AddNew>d__90[[System.__Canon, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()
   at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.__Canon, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext(System.Threading.Thread)
   at System.Runtime.CompilerServices.TaskAwaiter+<>c.<OutputWaitEtwEvents>b__12_0(System.Action, System.Threading.Tasks.Task)
   at System.Threading.Tasks.AwaitTaskContinuation.RunCallback(System.Threading.ContextCallback, System.Object, System.Threading.Tasks.Task ByRef)
   at System.Threading.Tasks.Task.RunContinuations(System.Object)
   at System.Threading.Tasks.Task`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].TrySetResult(System.Threading.Tasks.VoidTaskResult)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].SetExistingTaskResult(System.Threading.Tasks.Task`1<System.Threading.Tasks.VoidTaskResult>, System.Threading.Tasks.VoidTaskResult)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult()
   at AppPotion.Web.Ui.Shared.EntityEditor.EntityEditorComponentBase`1+<LoadEntityPermissionAsync>d__87[[System.__Canon, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()
   at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.__Canon, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext(System.Threading.Thread)
   at System.Runtime.CompilerServices.TaskAwaiter+<>c.<OutputWaitEtwEvents>b__12_0(System.Action, System.Threading.Tasks.Task)
   at System.Threading.Tasks.AwaitTaskContinuation.RunCallback(System.Threading.ContextCallback, System.Object, System.Threading.Tasks.Task ByRef)
   at System.Threading.Tasks.Task.RunContinuations(System.Object)
   at System.Threading.Tasks.Task`1[[System.__Canon, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].TrySetResult(System.__Canon)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[[System.__Canon, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].SetExistingTaskResult(System.Threading.Tasks.Task`1<System.__Canon>, System.__Canon)
   at AppPotion.Web.Ui.Framework.AppState+<GetEntityPermissionAsync>d__116.MoveNext()
   at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.__Canon, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.__Canon, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext(System.Threading.Thread)
   at System.Runtime.CompilerServices.TaskAwaiter+<>c.<OutputWaitEtwEvents>b__12_0(System.Action, System.Threading.Tasks.Task)
   at System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation+<>c__DisplayClass6_0.<GetActionLogDelegate>b__0()
   at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.ExecuteSynchronously(System.Threading.Tasks.TaskCompletionSource, System.Threading.SendOrPostCallback, System.Object)
   at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
   at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.ExecuteBackground(WorkItem)
   at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(System.Threading.Thread, System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef, System.Threading.Thread)
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
   at System.Threading.PortableThreadPool+WorkerThread.WorkerThreadStart()
The program '[19708] dotnet.exe' has exited with code 3221225477 (0xc0000005) 'Access violation'.

Expected behavior:
Tests should not crash dotnet tests and work as in previous versions of bunit.

Version info:

  • bUnit version: 1:19.14
  • .NET Runtime and Blazor version: .NET 7.0.5
  • OS type and version: Windows 10 Pro

Additional context:

I'm still trying to create a simple repro but reverting to 1.18.4 seems to resolve the issue.

@groogiam
Copy link
Author

groogiam commented May 9, 2023

I can reproduce with multiple versions. It appears this is linked to upgrading my projects to .NET 7. It happens more frequently the more tests I run at once.

@egil
Copy link
Member

egil commented May 9, 2023

Hi @groogiam,

Thanks for reaching out. Can you share the component under test and the test itself, so we can have a look and better help you.

@groogiam
Copy link
Author

It appears returning Task.CompletedTask is causing this. Our framework has a Action bar component which allows actions (buttons) to be configured which float at the top of a component.

    <Shared.EntityEditor.EditorActionBarComponent
        OnActionExecuting='ActionExecuting'
        OnActionExecuted='ActionExecuted'
        MenuActions='MenuActions'
        id='@($"Asset-Actions-Bar-{ComponentInstanceId}")'>
    </Shared.EntityEditor.EditorActionBarComponent>

This component then provides and executing and executed event callback. We have code gen that scaffolds these out to:

        private Task ActionExecuting()
        {
            IsLoading = true;
            return Task.CompletedTask;
        }
        
        private async Task ActionExecuted()
        {
            IsLoading = false;
            await Task.CompletedTask;
        }

For some reason Task.CompletedTask rather than actually awaiting something causes the infinite call to Bunit.Rendering.TestRenderer.LoadRenderTreeFrames when using the Click method.

E.g. cut.Find( $"#Asset-Actions-Bar-{cut.Instance.ComponentInstanceId} [title*='Reset form to allow creation of a new entity.']").Click();

Calling ClickAsync seems to prevent the exception from happening.

The strange thing is that the test that is actually failing is not the one that is calling Click. That test passes and another unrelated test that has Click call hangs and then gets the stack overflow exception.

This is the test that seems to be triggering the exception

    [Fact]
    public void PrimaryOwner_Lookup_Filter_Is_Reset_When_In_Add_New_Clicked()
    {
        using var ctx = TestContextWithMockHttp.CreateDefault();

        ctx.JSInterop.Mode = JSRuntimeMode.Loose;
        const string entityName = nameof(Asset);

        // setting up the entity
        var entity = new Asset
        {
            Id = 25,
            Name = "Asset 1",
            TypeDdName = AppPotion.Bd.DropDowns.AssetTypeDd.District.Name,
            TypeDdVal = AppPotion.Bd.DropDowns.AssetTypeDd.District.Value,
            StatusDdName = AppPotion.Bd.DropDowns.AssetStatusDd.Active.Name,
            StatusDdVal = AppPotion.Bd.DropDowns.AssetStatusDd.Active.Value,
            CountryDdName = AppPotion.Bd.DropDowns.CountryDd.USA.Name,
            CountryDdVal = AppPotion.Bd.DropDowns.CountryDd.USA.Value,
            CreatedBy = "System",
            CreatedDate = DateTime.Now,
            LastModifiedBy = "System",
            LastModifiedDate = DateTime.Now
        };

        // mocking the http calls which happen while initially loading the component
        ctx.MockHttp.AddEmptyDocumentManagementResponses();
        ctx.MockHttp.AddEmptyEntityTagResponses();
        ctx.MockHttp.When($"http://localhost/api/{entityName}/EntityPermission/25").RespondJson(new EntityPermissionDescriptor
        {
            EntitySystemName = entityName,
            EntityId = 25,
            Create = true,
            Delete = true,
            Read = true,
            Update = true
        });

        ctx.MockHttp.When($"http://localhost/api/{entityName}/EntityPermission/0").RespondJson(new EntityPermissionDescriptor
        {
            EntitySystemName = entityName,
            EntityId = 0,
            Create = true,
            Delete = true,
            Read = true,
            Update = true
        });

        ctx.MockHttp.When($"http://localhost/odata/{nameof(Asset)}({entity.Id})").RespondOData(entity);

        // inject service for setting the application in Admin User context
        ctx.SetLogin(BlazorTestHelpers.CreateAdminUserContext());
        var appState = ctx.Services.GetRequiredService<AppState>();
        appState.SetPrivateProperty(nameof(AppState.ViewPortSize), ViewPortSize.Large); //viewport size must be large so the lookup component renders as a grid.

        var parameters = new List<ComponentParameter>
        {
            ComponentParameter.CreateParameter(nameof(Ui_Asset_AssetEditor.EntityId),
                entity.Id)
        };

        var cut = ctx.RenderComponent<Ui_Asset_AssetEditor>(parameters.ToArray());

        //wait for web requests to complete
        cut.WaitForState(() => cut.Instance.EntityPermission != null);

        //Asserts

        // Click Add New Button
        cut.Find(
            $"#Asset-Actions-Bar-{cut.Instance.ComponentInstanceId} [title*='Reset form to allow creation of a new entity.']").Click();

        cut.Render();

       //Asserts
    }

This is the test where the exception is thrown.

    [Fact]
    public void PrimaryOwner_Lookup_Is_Filtered_For_Companies_With_AssetOwnerMap_When_Entity_In_Add_New_Mode()
    {
        using var ctx = TestContextWithMockHttp.CreateDefault();

        ctx.JSInterop.Mode = JSRuntimeMode.Loose;
        const string entityName = nameof(Asset);

        // mocking the http calls which happen while initially loading the component
        ctx.MockHttp.AddEmptyDocumentManagementResponses();
        ctx.MockHttp.AddEmptyEntityTagResponses();
        ctx.MockHttp.When($"http://localhost/api/{entityName}/EntityPermission/0").RespondJson(new EntityPermissionDescriptor
        {
            EntitySystemName = entityName,
            EntityId = 0,
            Create = true,
            Delete = true,
            Read = true,
            Update = true
        });

        // inject service for setting the application in Admin User context
        ctx.SetLogin(BlazorTestHelpers.CreateAdminUserContext());
        var appState = ctx.Services.GetRequiredService<AppState>();
        appState.SetPrivateProperty(nameof(AppState.ViewPortSize), ViewPortSize.Large); //viewport size must be large so the lookup component renders as a grid.

        var cut = ctx.RenderComponent<Ui_Asset_AssetEditor>();

        //wait for web requests to complete
        cut.WaitForState(() => cut.Instance.EntityPermission != null);

        //Asserts
    }

Thanks for your help on this.

@egil
Copy link
Member

egil commented May 11, 2023

This is a curious case. The LoadRenderTreeFrames method does call itself recursively, but we have never had anybody raise an issue like this before. I guess it could result in a stack overflow if the component tree is super deep.

In your test, do you have a very deep component tree (many nested components)?

@groogiam
Copy link
Author

The components go 2 maybe 3 deep. The more curious thing is that this only manifests when running tests in parallel. Running a single test via visual studio or resharper test window does not result in the exception.

It updated all our tests Replacing the Click() calls with awaited calls ClickAsync and this does seem to resolve the issue.

@egil
Copy link
Member

egil commented May 11, 2023

That Infinite loop could be the result of an inconsistent state in Blazors internal render tree which that method that seems to overflow is walking down.

Why that happens is not clear. Are each test totally independent? That is, no shared state between them?

@groogiam
Copy link
Author

As far as I can tell there is not. I spent some time today and yesterday looking through the tests and infrastructure. There are some private members declared in some of the tests used by multiple cases. My understanding of XUnit is that each of the tests method would spin up a new class instance so different instances would not be accessing those members. All the containers and mocks are defined locally in the methods and the services do not seem to have any static properties that would allow state to be shared across tests.

@egil
Copy link
Member

egil commented May 12, 2023

That is correct. Xunit creates a new instance of a test class every time, which means all instance fields are recreated.

@egil
Copy link
Member

egil commented May 14, 2023

I do not see anything in the tests you have shared that should cause this.

One small suggestion would be to wrap your HttpClient in an abstraction, e.g. IMyDataService, and instead create a mock or fake of that instead of mocking the HttpClient response and request. That will allow you to make the tests more deterministic by having a fake/mock implementation of IMyDataService that returns a completed task (Task.Completed, Task.FromResult()).

@David-Moreira
Copy link

David-Moreira commented May 16, 2023

Hello,

I have also the same problem on a new project I'm working on with MSTest + bUnit.

I joined this project, mid way so there were already alot of tests in place and it's hard to track what's causing it, but by enabling parallelize I get the same behavior as OP transiently, more frequently, so sometimes it would fail, sometimes not...
It does seem something to do with shared state. Just can't tell from where, codebase or actual Blazor engine.

One thing of note is that like OP, we are also using the WaitFor helpers, if that helps.

Also recently it was suddently happening very frequently even on sequential(non parallel) run, by removing the TestContext.Dispose from the TestCleanUp it fixed it, not sure it is to do with having an async dispose on it. But anyway I'm trusting the framework to still cleanup / Dispose properly of TestContext.


These kind of problems are always hard to diagnose, troubleshoot, if I figure out a reproducible I'll post it here.

@egil
Copy link
Member

egil commented May 16, 2023

Thanks for jumping in @David-Moreira. I am pretty sure that things are still being cleaned up without disposing of the TestContext.

Just want to confirm. Are you not seeing the issue at all when you do not dispose of the TestContext after the test is done running?

@David-Moreira
Copy link

So,
For Non Parallel runs:
it was happening like 5/10% of the time, mostly on the pipeline (probably because there's less resources, more added latency?).
Then after adding a few apparently regular tests, very similar to other tests we already had (related to crud pages, clicking buttons etc...) it started happening like practically on every run and the test running on crash would apparently always be different. So That was super strange.
So after removing the TestContext.Dispose explicit call it did immediately started going. I can't say it's gone completly but I haven't seen it yet, in about 10-15 runs.
Unfortunately I can't really post code, etc...(and there's alot of tests anyway) but if I ever see it again, figure out reproducible I'll let you know.

I don't see anything too fancy on your disposal code, but maybe the unawaited async disposal could have some side effect... Anyway this might be or not be the same apparent cause as OP.

For The Parallel runs:
I only enabled it once with 4 workers, after removing the explicit Dispose and I immediately got it failing, but I was just checking and the error wasn't the same stack overflow, so there might be another issue all together... :S I didn't bother exploring this one further, because tests still run acceptably fast non parallel for now.

@egil
Copy link
Member

egil commented May 16, 2023

Thanks, @David-Moreira, it is certainly giving us something to investigate.

The stack overflow that you and @groogiam are seeing indicates that the Blazor renderer's render tree is in an inconsistent state. That should not be something that could happen and something that happens outside of bUnits control. It could be that it happens when we are disposing of the Blazor renderer and we are not guarding against accessing the render tree at that point.

cc. @linkdotnet.

@egil
Copy link
Member

egil commented May 17, 2023

Hey @groogiam and @David-Moreira,

I am pushing a preview release to Nuget right now. It contains some code that may fix the issues you are experiencing. If you can, please give it a try in your code base. It is available here: https://www.nuget.org/packages/bunit/1.20.7-preview

You can also try to extend the default "wait for" timeout. The default is 1 second, which may sometimes be too short if running on slow hardware.

Just set the static property TestContext.DefaultWaitTimeout = TImeSpan.FromSeconds(30) or similar, to ensure that each test gets a chance to render before the timeout is reached. It should not affect normal test runtimes though.

Another thing you can try to aid debugging is to enable logging of the renderer logic. See https://bunit.dev/docs/misc-test-tips.html#capturing-logs-from-ilogger-in-test-output for more.

@egil egil added the investigate This issue require further investigation before closing. label May 17, 2023
@egil egil self-assigned this May 17, 2023
@David-Moreira
Copy link

@egil Appreciated the fast response.
Will let you know the results as soon as possible.

@David-Moreira
Copy link

David-Moreira commented May 18, 2023

Forgot to mention that I was on 1.18.4 and not on 1.19
First impressions:

  • Upon updating I had tests fail that never failed before, I had to put WaitFor in a few, gives the sense that either bUnit stopped waiting for initial renders, or somehow it's become faster that WaitFor is almost essential.
  • Have two that failed that already had WaitFor, basically now they behave slightly different, gotta take some time to investigate those... I disabled them for now just for the sake of testing.
  • I also started getting these that I didn't get before as soon as I activated 4 workers, on Syncfusion Charts components...
    image
    - EDIT: I got these also when switching back to 1 worker... all in all seems to be very unstable for me. I'll prioritize staying in 1.18, but i'll try testing the latest stable 1.19 when i have time just to see if I get the same problems as I'm getting with the preview.

I don't know if any of these help. And they're kinda unrelated to the original overflow problem...

I will try to keep posting feedback but as you know sometimes time is limited to be allocating to test troubleshooting.

@egil
Copy link
Member

egil commented May 18, 2023

Forgot to mention that I was on 1.18.4 and not on 1.19

We changed things in 1.19 that did forced some of the race conditions more into the light, so if you have more tests failing between the 1.18 and 1.19 that is not surprising.

First impressions:

  • Upon updating I had tests fail that never failed before, I had to put WaitFor in a few, gives the sense that either bUnit stopped waiting for initial renders, or somehow it's become faster that WaitFor is almost essential.

bUnit has never waited for components that perform async operations. If you have anything that is not completely synchronous (returning Task.CompletedTask counts), then you need to use one of the WaitFor methods. E.g. if you have a Task.Delay or similar.

  • Have two that failed that already had WaitFor, basically now they behave slightly different, gotta take some time to investigate those... I disabled them for now just for the sake of testing.
  • I also started getting these that I didn't get before as soon as I activated 4 workers, on Syncfusion Charts components...
    image
    - EDIT: I got these also when switching back to 1 worker... all in all seems to be very unstable for me. I'll prioritize staying in 1.18, but i'll try testing the latest stable 1.19 when i have time just to see if I get the same problems as I'm getting with the preview.

I don't know if any of these help. And they're kinda unrelated to the original overflow problem...

I will try to keep posting feedback but as you know sometimes time is limited to be allocating to test troubleshooting.

My experience with our own tests is that with the latest preview they are completely stable, but I did have to increase the default wait for timeout when running on CI or slow hardware. My AMD 5950x has no problem ever. 5 year old laptop and GitHub actions, especially GitHub actions on windows and osx, has.

@groogiam
Copy link
Author

groogiam commented May 18, 2023

@egil The change does not seem to help the original issue (non async click calls) on my local machine or CI. However it does seem stable on the updated code that utilizes async calls for actions. Setting TestContext.DefaultWaitTimeout to 30 seconds does seem to significantly slow down the test execution though. It seems like quite a few of my tests wait the entire timeout for some reason. Thanks.

@egil
Copy link
Member

egil commented May 18, 2023

@egil The change does not seem to help the original issue (non async click calls) on my local machine or CI. However it does seem stable on the updated code that utilizes async calls for actions.

Are you disposing the TestContext at the end of the test method? Doing so will stop the renderer and prevent any further renders from happening.

Also, try enabling logging as mentioned previously. If you have tests that sometimes completes successfully and sometimes not, you can compare the log output between the two and get a good idea of what is going on.

If you share the log statements here, we call also help parse them.

Setting TestContext.DefaultWaitTimeout to 30 seconds does seem to significantly slow down the test execution though. It seems like quite a few of my tests wait the entire timeout for some reason. Thanks.

If that is the case, then you are probably not writing the right assertions. The "wait for" methods will attempt the assertion/check you pass to them synchronously after each render is completed (but before OnAfterRender runs). If you have tests that block/waits for the entire timeout period, then that very likely means that the stat/elements/assertions you are waiting for never happens.

The reason why it works to extend the (default) timeout is that there are cases where the machine running the tests are simply so slow that none or not enough renders cycles happens which takes the component under test to the desired state, before the timeout is reached.

@David-Moreira
Copy link

I might be going off thread here...But are you guys using MSTest or have used with bUnit?

I'm thinking most of my problems might just straight up be to do with Thread Safety, I'm not sure how MSTest runs the tests that are inside a single [TestClass] (class with all the tests [TestMethod] inside) but it might just not be running them quite independently as one would have though.

The new tests that were failing because of some concurrent update (what the hell), I decided to refactor them into xUnit, put the [TestInitialize] into a ctor, and bam started passing.

Anyway since removing the [TestCleanup] with the explicit TestContext.Dispose I haven't seen the stack overflow error which was the original issue of this thread.

@egil
Copy link
Member

egil commented May 18, 2023

I might be going off thread here...But are you guys using MSTest or have used with bUnit?

We use xUnit. But I do not think MSTest should be a problem. Haven't heard anybody say so.

I'm thinking most of my problems might just straight up be to do with Thread Safety, I'm not sure how MSTest runs the tests that are inside a single [TestClass] (class with all the tests [TestMethod] inside) but it might just not be running them quite independently as one would have though.

The new tests that were failing because of some concurrent update (what the hell), I decided to refactor them into xUnit, put the [TestInitialize] into a ctor, and bam started passing.

I would love an example of a test that works on xUnit and not in MSTest. bUnit is supposed to work with all major test frameworks.

Anyway since removing the [TestCleanup] with the explicit TestContext.Dispose I haven't seen the stack overflow error which was the original issue of this thread.

I explicitly fixed the dispose problem in the latest 1.20 preview release, so it should be safe to clean up and tell the renderer that you are done with the test.

@David-Moreira
Copy link

David-Moreira commented May 18, 2023

I would love an example of a test that works on xUnit and not in MSTest. bUnit is supposed to work with all major test frameworks.

I forgot a very important note, the SF Dictionary concurrent issue, did not seem to happen at all running test locally, only on pipeline everytime. In this case it's in BitBucket. And well I was not seeing any reason why some concurrent access would be done, so I decided to try a diff testing framework and it worked... Not saying it's the testing framework fault, there might be more at play here... but that was what immediately worked in this case.

Anyway I'll see if I get around to it.
Would have to try with GHActions. Anyway I'll hit you up with an actual repro if I get around to it as that's way more useful to you.

The original issue does not seem a problem anymore, at least for me as I'm even fine without the explicit Dispose. That's something that was already in the project I am now working on and I don't think it's needed. Thanks.

@egil
Copy link
Member

egil commented May 18, 2023

Appreciate that. As far as I know, MSTest does not have a synchronization context, that could be the culprit. NUnit and xUnit have that.

Also, with xUnit, you have to try to have shared state between tests, e.g. a shared TestContext. I think that is an easier trap to fall into with MSTest and NUnit.

@egil
Copy link
Member

egil commented May 23, 2023

@groogiam what's the status from you? Still seeing the original issue with the stack overflow?

If not, I would like to close this issue.

@groogiam
Copy link
Author

@egil Yes, I still see the stack overflow on code that does not use the Async overloads even though each unit tests disposes it's context. Everything works fine if I use the async overloads.

Still seeing intermittent failures with WaitForX but I'm not sure that is related to this issue.

@egil
Copy link
Member

egil commented May 23, 2023

@egil Yes, I still see the stack overflow on code that does not use the Async overloads even though each unit tests disposes it's context. Everything works fine if I use the async overloads.

Please bare with me. Too many threads in this issue for me to keep track of things. Can you simplify what you say here to a basic example of when this happens vs. when it doesn't. It sounds like you are changing between a sync vs async version where it works in one but not the others.

Still seeing intermittent failures with WaitForX but I'm not sure that is related to this issue.

In general, if you have anything in a component that causes an async re-render (StateHasChanged) of the component, e.g. a timer or delay, that re-render may happen before or after the "WaitFor" call in your test, since the test code is running in one thread and the re-render happens on another thread.

If a trigger of a StateHasChanged can be controlled from the test, then you can make your tests deterministic. E.g. if a re-render is triggered by a timer, consider something like https://www.nuget.org/packages/TimeProviderExtensions to control time during testing.

@groogiam
Copy link
Author

@egil Yes, I still see the stack overflow on code that does not use the Async overloads even though each unit tests disposes it's context. Everything works fine if I use the async overloads.

Please bare with me. Too many threads in this issue for me to keep track of things. Can you simplify what you say here to a basic example of when this happens vs. when it doesn't. It sounds like you are changing between a sync vs async version where it works in one but not the others.

This still causes a stack overflow exception

cut.Find(
            $"#Actions-Bar-{cut.Instance.ComponentInstanceId} [title*='Reset form to allow creation of a new entity.']").Click();

where this does not

        await cut.Find(
            $"#Actions-Bar-{cut.Instance.ComponentInstanceId} [title*='Reset form to allow creation of a new entity.']").ClickAsync(new MouseEventArgs());

I've already modified my tests to use the async calls and that seems to works just fine with the updated version. So if you want to call it good and close the issue I would not have any problem with that. But this is essentially the thing that caused me to open the issue in the first place.

Still seeing intermittent failures with WaitForX but I'm not sure that is related to this issue.

In general, if you have anything in a component that causes an async re-render (StateHasChanged) of the component, e.g. a timer or delay, that re-render may happen before or after the "WaitFor" call in your test, since the test code is running in one thread and the re-render happens on another thread.

If a trigger of a StateHasChanged can be controlled from the test, then you can make your tests deterministic. E.g. if a re-render is triggered by a timer, consider something like https://www.nuget.org/packages/TimeProviderExtensions to control time during testing.

The issue seems to be timing with the async network calls in the OnInitializedAsync. I have code that essentially waits for a property that get set by one of these network calls directly after the initial render is called, but when under load things will just randomly hang for whatever the wait timeout is and fail. We can table this for now. I can submit another issue when I can finally work out a repro. It could very well be something silly I am doing. Sorry to muddy the waters.

@egil
Copy link
Member

egil commented May 24, 2023

Let's keep the issue open for now, but focus on the overflow. Can you share the code that is in the "onclick" handler that is being invoked? It may give me a hint at what I should look for.

Just to round off the other issue: You are probably able to make the tests deterministic by not injecting HttpClient directly into your components. Instead create an abstraction, e.g.:

public interface IDataService
{
  Task<EntityPermissionDescriptor> GetPermissionDescriptor(string entityName, int id);
  Task<Entity> GetEntity(string entityName, int id);
}

// use in production
public class DataService : IDataService
{
  private readonly HttpClient httpClient;

  public DataService(HttpClient httpClient)
  {
    this.httpClient = httpClient;
  }

  public async Task<EntityPermissionDescriptor> GetPermissionDescriptor(string entityName, int id)
  {
    // copy httpclient call code from components in here
  }

  public Task<Entity> GetEntity(string entityName, int id)
  {
    // copy httpclient call code from components in here
  }
}

// In your blazor app, in register the service 
// via Services.AddScoped/AddSingleton<IDataService, DataService>()

// use this version in testing
public class FakeDataService : IDataService
{ 
  private readonly List<EntityPermissionDescriptor> descriptors = new();

  public void SetPermissionDescriptor(EntityPermissionDescriptor descriptor)
  {
    descriptors.Add(descriptor);
  }

  public Task<EntityPermissionDescriptor> GetPermissionDescriptor(string entityName, int id)
    => Task.FromResult(descriptors.Single(x => x.EntitySystemName == entityName && x.EntityId == id));

  // todo add other overloads
}

Then in your tests, you can do something like this:

[Fact]
public void PrimaryOwner_Lookup_Is_Filtered_For_Companies_With_AssetOwnerMap_When_Entity_In_Add_New_Mode()
{
    using var ctx = TestContextWithMockHttp.CreateDefault();
    var dataService = new FakeDataService();
    cts.Services.AddSingleton<IDataService>(dataService);
    ctx.JSInterop.Mode = JSRuntimeMode.Loose;
    const string entityName = nameof(Asset);

    // mocking the http calls which happen while initially loading the component
    dataService.SetPermissionDescriptor(new EntityPermissionDescriptor
    {
        EntitySystemName = entityName,
        EntityId = 0,
        Create = true,
        Delete = true,
        Read = true,
        Update = true
    });

    // inject service for setting the application in Admin User context
    ctx.SetLogin(BlazorTestHelpers.CreateAdminUserContext());
    var appState = ctx.Services.GetRequiredService<AppState>();
    appState.SetPrivateProperty(nameof(AppState.ViewPortSize), ViewPortSize.Large); //viewport size must be large so the lookup component renders as a grid.

    var cut = ctx.RenderComponent<Ui_Asset_AssetEditor>();

    //wait for web requests to complete
    cut.WaitForState(() => cut.Instance.EntityPermission != null);

    //Asserts
}

Because the GetPermissionDescriptor in FakeDataService returns a completed task immediately, the renderer never goes into an asynchronous state, and that should help a lot with the determinism of your tests.

Having the IDataService abstraction also makes it possible to add caching or similar in the production version that will be shared by all components that may want to call the API, so there are definitely some benefits to having an abstraction like that besides making testing easier.

@groogiam
Copy link
Author

Let's keep the issue open for now, but focus on the overflow. Can you share the code that is in the "onclick" handler that is being invoked? It may give me a hint at what I should look for.

    private async Task Refresh()
    {
        if (!await ConfirmOverwriteUnsavedChanges())
        {
            return;
        }

        IsLoading = true;

        try
        {
            await LoadEntityInternal(); //makes odata http request

            await LoadEntityPermissionAsync(); //checks singleton service for cache entry otherwise makes http request for the permission.
        }
        finally
        {
            IsLoading = false;
        }

        await Ctx.MessageBus.PublishAsync(new EntityRefreshedMessage<TEntity>
        {
            Sender = this,
            Entity = Entity
        });

        await Ctx.MessageBus.PublishAsync(new EntityRefreshedMessage
        {
            Sender = this,
            EntityTypeName = EntityType.Name,
            Entity = Entity
        });

        Ctx.Notifier.SystemNotifySuccess("Refresh Complete");
    }

@egil
Copy link
Member

egil commented May 24, 2023

A few questions:

  1. What path through the code does the test that fails trigger? I.e. does ConfirmOverwriteUnsavedChanges return true or false?
  2. Which of the statements causes changes to the rendered output? e.g. will LoadEntityInternal affect the rendered markup? What about Ctx.MessageBus.PublishAsync?

@groogiam
Copy link
Author

A few questions:

  1. What path through the code does the test that fails trigger? I.e. does ConfirmOverwriteUnsavedChanges return true or false?

This returns false in the tests.

  1. Which of the statements causes changes to the rendered output? e.g. will LoadEntityInternal affect the rendered markup? What about Ctx.MessageBus.PublishAsync?

The message bus publishes don't do anything in the tests. In a live app other editor or list components might handle them but here nothing is wired to them.

The LoadEntityInternal and LoadEntityPermissionAsync may have hooks attached which update properties on the component.

In most cases none of these will explicitly called StateHasChanged as it is assumed the component will re-render after the method has completed.

The tests in most cases actually verify properties have been set on the instance as the affected markup would be in a sub component usually a lookup component. Our assertions on these properties pass without issue but the background execution seems to continue even after the context is disposed causing the stack overflow.

@egil
Copy link
Member

egil commented May 27, 2023

Ok. Thank you for the update.

The difference between Click and await ClickAsync is that in the latter, you are waiting for the eventhandler to finish running.

What are you doing in the test after triggering the onclick handler?

@groogiam
Copy link
Author

In this case we are calling a Render() then asserting some internal state on the component. Depending on the scenario sometimes we may also have a WaitForState or WaitForElement.

@egil
Copy link
Member

egil commented May 29, 2023

Interesting. Why are you calling cut.Render()?

Calling it just causes the component to rerender. But doing this right after calling Click, which also causes the component to render, seems redundant on the surface, but I gather there is an edge case that I haven't thought of.

Either way, calling cut.Render() should not cause an overflow. I'll investigate some more.

Can you help with a stack trace for when this happens in the Click() scenario?

@groogiam
Copy link
Author

Interesting. Why are you calling cut.Render()?

Calling it just causes the component to rerender. But doing this right after calling Click, which also causes the component to render, seems redundant on the surface, but I gather there is an edge case that I haven't thought of.

In this case cut.Render seem to be there mostly for timing. As we were not awaiting the click the asserts on the state would randomly fail without the render call.

        cut.Render();

        // Verify Filters are Reset
        existingFilter = cut.Instance.PrimaryOwnerIdLookupFilters.OfType<FilterDescriptor>().FirstOrDefault(filter => filter.Member == "OwnedAssets/any(p: {0})|p.AssetId");

        Assert.NotNull(existingFilter); //this randomly fails without the render call above

Either way, calling cut.Render() should not cause an overflow. I'll investigate some more.

Can you help with a stack trace for when this happens in the Click() scenario?

The exception does not seem to be thrown in this test method directly. It is thrown by the test referenced in the original post when run in conjunction with other tests that have this pattern of non awaited click handlers.

@egil
Copy link
Member

egil commented May 31, 2023

Ok. Thanks for clarifying. Calling Render in the scenario you describe should not make a difference, and it is accidental if it seems like it does.

Perhaps this will work instead:

cut.WaitForAssertion(() => cut.Instance.PrimaryOwnerIdLookupFilters.OfType<FilterDescriptor>().First(filter => filter.Member == "OwnedAssets/any(p: {0})|p.AssetId"));

Calling click will cause the renderer to complete any async renders as fast as the async task that is being awaited completes.

Calling Render will just invoke SetParameterAsync without passing in any new parameters, which is probably not what you want.

@groogiam
Copy link
Author

I agree it is a happy accident that it works using the extra render. Both awaiting the ClickAsync or the WaitForAssertion approach suggested above seem to alleviate the need for the extra render. However we still get the stack overflow exception on subsequent tests when using the WaitForAssertion method suggested above as the test is still not async. This seems to suggest there is some unknown shared state between the rendering of these various tests.

@egil
Copy link
Member

egil commented May 31, 2023

I agree it is a happy accident that it works using the extra render. Both awaiting the ClickAsync or the WaitForAssertion approach suggested above seem to alleviate the need for the extra render. However we still get the stack overflow exception on subsequent tests when using the WaitForAssertion method suggested above as the test is still not async. This seems to suggest there is some unknown shared state between the rendering of these various tests.

If you are disposing of the TestContext between each test then that should not be the case, but I would need to see the full test setup you have to understand exactly what is going on.

Is it possible you can share it in private?

@groogiam
Copy link
Author

groogiam commented Jun 1, 2023

I'd be happy to share greater details of the repository in private. Please let me know you would like to proceed with that. Thanks.

@egil
Copy link
Member

egil commented Jun 1, 2023

I'd be happy to share greater details of the repository in private. Please let me know you would like to proceed with that. Thanks.

If you have the code on github, you can give add me to the repo and I can look there. Otherwise, feel free to zip up the relevant parts of the source code and send it to me via email (egil at assimilated.dk).

EDIT: include points to the test in question that are failing, and test log from runs that have failed.

@egil
Copy link
Member

egil commented Jun 2, 2023

Sorry wrong issue :)

@groogiam
Copy link
Author

groogiam commented Jun 2, 2023

@egil Just sent an email with zipped bUnit test project. Let me know if you need anything else.

@egil
Copy link
Member

egil commented Jun 3, 2023

@egil Just sent an email with zipped bUnit test project. Let me know if you need anything else.

Thank you. Ill take a look as soon as possible and report back.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
investigate This issue require further investigation before closing.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants