Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,18 @@ public override bool TryGetContract<TContract>([NotNullWhen(true)] out TContract
return true;
}

if (!_tryGetContractVersion(TContract.Name, out string? version))
Func<Target, IContract>? creator;
if (_tryGetContractVersion(TContract.Name, out string? version))
{
failureReason = $"Target does not support contract '{typeof(TContract).Name}'.";
return false;
if (!_creators.TryGetValue((typeof(TContract), version), out creator))
{
failureReason = $"Target supports contract '{typeof(TContract).Name}' version {version}, but no implementation is registered for that version.";
return false;
}
}

if (!_creators.TryGetValue((typeof(TContract), version), out Func<Target, IContract>? creator))
else if (!_creators.TryGetValue((typeof(TContract), string.Empty), out creator))
{
failureReason = $"Target supports contract '{typeof(TContract).Name}' version {version}, but no implementation is registered for that version.";
failureReason = $"Target does not support contract '{typeof(TContract).Name}'.";
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,13 @@ private string MakeContractsJson()
}
}

public bool TryCreateTarget(DescriptorBuilder descriptor, [NotNullWhen(true)] out ContractDescriptorTarget? target)
public bool TryCreateTarget(DescriptorBuilder descriptor, [NotNullWhen(true)] out ContractDescriptorTarget? target, Action<ContractRegistry>[]? contractRegistrations = null)
{
if (_created)
throw new InvalidOperationException("Context already created");
_created = true;
ulong contractDescriptorAddress = descriptor.CreateSubDescriptor(ContractDescriptorAddr, JsonDescriptorAddr, ContractPointerDataAddr);
MockMemorySpace.MemoryContext memoryContext = GetMemoryContext();
return ContractDescriptorTarget.TryCreate(contractDescriptorAddress, memoryContext.ReadFromTarget, memoryContext.WriteToTarget, (_, _, _) => throw new NotImplementedException("Tests do not provide GetTargetThreadContext"), (ulong _, out ulong _) => throw new NotImplementedException("Tests do not provide AllocVirtual"), [Contracts.CoreCLRContracts.Register], out target);
return ContractDescriptorTarget.TryCreate(contractDescriptorAddress, memoryContext.ReadFromTarget, memoryContext.WriteToTarget, (_, _, _) => throw new NotImplementedException("Tests do not provide GetTargetThreadContext"), (ulong _, out ulong _) => throw new NotImplementedException("Tests do not provide AllocVirtual"), contractRegistrations ?? [Contracts.CoreCLRContracts.Register], out target);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using Microsoft.Diagnostics.DataContractReader.Contracts;
using Microsoft.Diagnostics.DataContractReader.TestInfrastructure;
using Microsoft.Diagnostics.DataContractReader.TestInfrastructure.ContractDescriptor;
using Xunit;

namespace Microsoft.Diagnostics.DataContractReader.Tests.ContractDescriptor;

/// <summary>
/// Tests contract-version resolution through a real <see cref="ContractDescriptorTarget"/>
/// (and thus the production <c>CachingContractRegistry</c>), in particular the
/// empty-string "default" registration used as a fallback when the target does not
/// advertise a version for a contract.
/// </summary>
public class ContractRegistrationTests
{
private interface IFakeContract : IContract
{
static string IContract.Name { get; } = "FakeContract";
string Tag { get; }
}

private sealed class FakeContract(string tag) : IFakeContract
{
public string Tag => tag;
}

private static ContractDescriptorTarget CreateTarget(
MockTarget.Architecture arch,
string[] advertisedContracts,
Action<ContractRegistry> registerFake)
{
TargetTestHelpers helpers = new(arch);
ContractDescriptorBuilder builder = new(helpers);
ContractDescriptorBuilder.DescriptorBuilder descriptor = new(builder);
descriptor.SetTypes(new Dictionary<DataType, Target.TypeInfo>())
.SetGlobals(Array.Empty<(string, ulong, string?)>())
.SetContracts(advertisedContracts);

bool created = builder.TryCreateTarget(
descriptor,
out ContractDescriptorTarget? target,
[Contracts.CoreCLRContracts.Register, registerFake]);
Assert.True(created);
return target!;
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void NoAdvertisedVersion_FallsBackToDefaultRegistration(MockTarget.Architecture arch)
{
// Target advertises no version for FakeContract -> the empty-string
// "default" registration is used.
ContractDescriptorTarget target = CreateTarget(
arch,
advertisedContracts: [],
registerFake: static r => r.Register<IFakeContract>(string.Empty, static t => new FakeContract("default")));

Assert.True(target.Contracts.TryGetContract<IFakeContract>(out IFakeContract? contract));
Assert.Equal("default", contract.Tag);
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void NoAdvertisedVersion_NoDefaultRegistration_Fails(MockTarget.Architecture arch)
{
// FakeContract is neither advertised nor registered.
ContractDescriptorTarget target = CreateTarget(
arch,
advertisedContracts: [],
registerFake: static _ => { });

Assert.False(target.Contracts.TryGetContract<IFakeContract>(out IFakeContract? contract));
Assert.Null(contract);
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void AdvertisedVersion_UsesVersionedRegistration_NotDefault(MockTarget.Architecture arch)
{
// Target advertises FakeContract (the builder emits version "c1"); both a
// versioned and a default registration exist, and the advertised version
// must win.
ContractDescriptorTarget target = CreateTarget(
arch,
advertisedContracts: ["FakeContract"],
registerFake: static r =>
{
r.Register<IFakeContract>("c1", static t => new FakeContract("v1"));
r.Register<IFakeContract>(string.Empty, static t => new FakeContract("default"));
});

Assert.True(target.Contracts.TryGetContract<IFakeContract>(out IFakeContract? contract));
Assert.Equal("v1", contract.Tag);
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void AdvertisedVersion_NoMatchingRegistration_DoesNotFallBackToDefault(MockTarget.Architecture arch)
{
// Target advertises FakeContract (version "c1"), but only a default ("")
// registration exists. This is a version-skew failure and must NOT
// silently use the default registration.
ContractDescriptorTarget target = CreateTarget(
arch,
advertisedContracts: ["FakeContract"],
registerFake: static r => r.Register<IFakeContract>(string.Empty, static t => new FakeContract("default")));

Assert.False(target.Contracts.TryGetContract<IFakeContract>(out IFakeContract? contract));
Assert.Null(contract);
}
}
Loading