Skip to content

False negative with "/p:MergeWith" async/await branches #275

@MarcoRossignoli

Description

@MarcoRossignoli

I moved discussion from old PR to new issue.


@pape77 issue #158 (comment)

I have a similar issue with this "ghost branches" which are never covered.
My code looks like this

public class KeyProvider : IKeyProvider
{
    private readonly IKeyRepository _repository;
    private readonly IMemoryCache _cache;

    public KeyProvider(IKeyRepository repository, IMemoryCache cache)
    {
        _repository = repository;
        _cache = cache;
    }

    public async Task<Key> GetByUuidAndTypeAsync(Guid uuid, KeyType type, CancellationToken cancellationToken)
    {
        return await _cache.GetOrCreateAsync($"uuid-{uuid.ToString()}-key-{type}",
            async entry => await _repository.FindOrCreateAsync(uuid, type, cancellationToken));
    }

public class Key
{
public int Id { get; set; }

    public Guid Uuid { get; set; }

    [Required]
    public Guid Kid { get; set; }

    [Required]
    public KeyType KeyType { get; set; }
}

public enum KeyType
{
Cenc,
Fairplay,
Aes
}
}
This code generates 4 branches, 2 of which I'm never able to cover

my tests:

public class KeyProviderTests
{
private readonly Mock _keyRepositoryMock;
private readonly MemoryCache _memoryCache;
private readonly Guid _testGuid = Guid.NewGuid();
private readonly KeyProvider _keyProvider;

    public KeyProviderTests()
    {
        _memoryCache = new MemoryCache(new MemoryCacheOptions());

        _keyRepositoryMock = new Mock<IKeyRepository>();

        _keyProvider = new KeyProvider(_keyRepositoryMock.Object, _memoryCache);
    }

    [Theory(DisplayName = "Should call repository method with correct values")]
    [InlineData(KeyType.Cenc)]
    [InlineData(KeyType.Aes)]
    [InlineData(KeyType.Fairplay)]
    public async Task GetByUuidAndTypeAsync01(KeyType keyType)
    {
        _keyRepositoryMock.Setup(kr =>
                kr.FindOrCreateAsync(_testGuid, keyType, It.IsAny<CancellationToken>()))
            .ReturnsAsync(new Key());

        await _keyProvider.GetByUuidAndTypeAsync(_testGuid, keyType, new CancellationToken());

        _keyRepositoryMock.Verify(
            kr => kr.FindOrCreateAsync(_testGuid, keyType, It.IsAny<CancellationToken>()), Times.Once);
    }

    [Theory(DisplayName = "Should get key from cache")]
    [InlineData(KeyType.Cenc)]
    [InlineData(KeyType.Aes)]
    [InlineData(KeyType.Fairplay)]
    public async Task GetByUuidAndTypeAsync02(KeyType keyType)
    {
        var cacheKey = $"uuid-{_testGuid.ToString()}-key-{keyType}";
        _memoryCache.Set(cacheKey, new Key());
       
        await _keyProvider.GetByUuidAndTypeAsync(_testGuid, keyType, new CancellationToken());

        _keyRepositoryMock.Verify(
            kr => kr.FindOrCreateAsync(_testGuid, keyType, It.IsAny<CancellationToken>()), Times.Never);
    }

}
The generated branches in my coverage.json are (notice the MoveNext() thing):

 "Keys.Application.Services.KeyProvider/<>c__DisplayClass3_0/<<GetByUuidAndTypeAsync>b__0>d": {
    "System.Void Keys.Application.Services.KeyProvider/<>c__DisplayClass3_0/<<GetByUuidAndTypeAsync>b__0>d::MoveNext()": {
      "Lines": {
        "27": 9
      },
      "Branches": [
        {
          "Line": 27,
          "Offset": 8,
          "EndOffset": 14,
          "Path": 0,
          "Ordinal": 0,
          "Hits": 3
        },
        {
          "Line": 27,
          "Offset": 81,
          "EndOffset": 83,
          "Path": 0,
          "Ordinal": 2,
          "Hits": 0
        },
        {
          "Line": 27,
          "Offset": 8,
          "EndOffset": 119,
          "Path": 1,
          "Ordinal": 1,
          "Hits": 0
        },
        {
          "Line": 27,
          "Offset": 81,
          "EndOffset": 147,
          "Path": 1,
          "Ordinal": 3,
          "Hits": 3
        }
      ]
    }
  },

Line 27 in my code is precisely the async await one:
async entry => await _repository.FindOrCreateAsync(uuid, type, cancellationToken));

hope it's useful


Repro keys.zip

/cc @kevindqc /@tonerdo

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions