Why is this test failing?

NewTestScriptSimplePasses (0.021s)

Expected: not allocates GC memory
But was: <NUnit.Framework.TestDelegate>

at NewTestScript.NewTestScriptSimplePasses () [0x00013] in C:\Prgdir\UsingTest\New Unity Project\Assets\Tests\NewTestScript.cs:16

My code is not supposed to allocate at all, but this test fails. It doesn’t explicitly say that it fails because of allocations though, so before to continue the investigation I need to be sure about it.

Can you show the code of the test?

sure but I would prefer you answering my question because I needed it to decide if to open a bug or not

right I noticed I didn’t ask the question I actually wanted to ask. The question is: what does But was: <NUnit.Framework.TestDelegate> mean?

code : SharpLab

Ahhh, right. What’s happening there is that NUnit has a standard way of displaying constraint failures - it shows you the constraint (in this case, ‘not allocates GC memory’) and the ‘value’ that did not meet the constraint, in this case a delegate that was generated from the function you passed in. The fact that it’s reporting that constraint as having failed means that the delegate you passed is indeed causing GC memory to be allocated when it is invoked.

The message could definitely be improved… I’m a little puzzled because it looks like the code for AllocatingGCMemoryResult does override the method for generating the output, it should be producing a message like “The provided delegate made 3 GC allocations” or similar. We do have tests that are checking AssertionException.Message for a string along those lines, so it might be a bug in the way NUnit generates the final result for the console.

1 Like

Ok thank you so much. Since the attached code must not allocate, I’ll report it as a bug. Let me know if you have any thoughts on it

One thing to check - if you execute the delegate once first, and then do the assertion for it, do you still see it fail? I think I’ve seen issues in the past on Mono where the initial JIT of the code can cause GC allocations.

1 Like

yes that worked! Of course I suggest to try to find a way to execute the Delegate before testing it because it doesn’t make much sense

We can’t guarantee that the delegate will not have side effects, so it’s not safe for us to just execute it for you automatically.

I think we might be able to force JIT of the delegate itself, which will help with some cases, but I don’t think there is any way for us to guarantee that no JIT happens at all during the assertion. At the very least we should mention this in the docs though. Let me see what I can do about that…

1 Like

Ok I understand it but that would mean that the test fails without a good reason to fail under the test point of view.

BTW one thing that is important to note is that the using line showed in my code is what cause the allocation. If I comment it, the test won’t fail.

I’ve opened a PR that will replace the ‘NUnit.Framework.TestDelegate’ with something more useful like ‘3 GC allocations’, as well as improving the docs a bit. Hopefully this can ship in the next release of the test framework.

Looking at the IL for your code, the using line is doing this:

                IL_0024: ldarg.0
                IL_0025: ldstr "Svelto.ECS - Entities Submission"
                IL_002a: newobj instance void PlatformProfiler::.ctor(string)
                IL_002f: stfld valuetype PlatformProfiler NewTestScript/'<UsingTest>d__1'::'<>7__wrap1'

The newobj is what is causing the allocation. I think it’s because your UsingTest method is an iterator block; if I change it so that it is not, the IL changes to this:

        IL_0000: ldloca.s 0
        IL_0002: ldstr "Svelto.ECS - Entities Submission"
        IL_0007: call instance void PlatformProfiler::.ctor(string)

that’s a newobj of a struct, so it shouldn’t cause any allocation. However I now wonder if it boxes, but it shoulnd’t:

The newobj instruction creates a new object or a new instance of a value type
Value types are not usually created using newobj. They are usually allocated either as arguments or local variables, using newarr (for zero-based, one-dimensional arrays), or as fields of objects. Once allocated, they are initialized using Initobj. However, the newobj instruction can be used to create a new instance of a value type on the stack, that can then be passed as an argument, stored in a local, and so on.

If you were right, the warm up trick wouldn’t have worked btw

You’re right - I thought perhaps it was an iterator block issue, but writing the same thing out explicitly results in the same IL.

[quote]*
*[/quote]
*

True; if the warmup trick works then it does imply what we should be looking for is a one-time operation, not something in the IL that would run every time.

One thing you could try is to use Marshal.GetFunctionPointerForDelegate to force the delegate to be JITted, without actually executing it. If doing that eliminates the allocation from the assert, then I think we can conclude that the allocation is coming from the JIT process itself.

1 Like