Skip to content

Commit

Permalink
[cdac] Make contracts tests use mock/placeholder target instead of ac…
Browse files Browse the repository at this point in the history
…tual target (#110027)

- Separate helpers for testing an actual contract descriptor from helpers for mock memory
  - Tests/helpers for the contract descriptor are now under the `ContractDescriptor` folder
- Make all contracts tests use the mock target instead of creating a contract descriptor and using the `ContractDescriptorTarget`
- Update root namespaces from `Microsoft.Diagnostics.DataContractReader.UnitTests` -> `Microsoft.Diagnostics.DataContractReader.Tests` to match assembly name
  • Loading branch information
elinor-fung authored Nov 21, 2024
1 parent c31bb93 commit 09b30a4
Show file tree
Hide file tree
Showing 32 changed files with 989 additions and 952 deletions.
2 changes: 1 addition & 1 deletion src/native/managed/cdacreader/tests/CodeVersionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
using Moq;
using Xunit;

namespace Microsoft.Diagnostics.DataContractReader.UnitTests;
namespace Microsoft.Diagnostics.DataContractReader.Tests;

using MockCodeVersions = MockDescriptors.CodeVersions;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// 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 System.Diagnostics.CodeAnalysis;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace Microsoft.Diagnostics.DataContractReader.Tests.ContractDescriptor;

internal class ContractDescriptorBuilder : MockMemorySpace.Builder
{
// These addresses are arbitrary and are used to store the contract descriptor components.
// They should not overlap with any other heap fragment addresses.
private const ulong ContractDescriptorAddr = 0xaaaaaaaa;
private const uint JsonDescriptorAddr = 0xdddddddd;
private const uint ContractPointerDataAddr = 0xeeeeeeee;

private bool _created = false;

private IReadOnlyCollection<string> _contracts;
private IDictionary<DataType, Target.TypeInfo> _types;
private IReadOnlyCollection<(string Name, ulong? Value, uint? IndirectIndex, string? TypeName)> _globals;
private IReadOnlyCollection<ulong> _indirectValues;

public ContractDescriptorBuilder(TargetTestHelpers targetTestHelpers)
: base(targetTestHelpers)
{ }

public ContractDescriptorBuilder SetContracts(IReadOnlyCollection<string> contracts)
{
if (_created)
throw new InvalidOperationException("Context already created");
_contracts = contracts;
return this;
}

public ContractDescriptorBuilder SetTypes(IDictionary<DataType, Target.TypeInfo> types)
{
if (_created)
throw new InvalidOperationException("Context already created");
_types = types;
return this;
}

public ContractDescriptorBuilder SetGlobals(IReadOnlyCollection<(string Name, ulong Value, string? TypeName)> globals)
{
if (_created)
throw new InvalidOperationException("Context already created");
if (_globals != null)
throw new InvalidOperationException("Globals already set");
_globals = globals.Select(g => (g.Name, (ulong?)g.Value, (uint?)null, g.TypeName)).ToArray();
_indirectValues = null;
return this;
}

public ContractDescriptorBuilder SetGlobals(IReadOnlyCollection<(string Name, ulong? Value, uint? IndirectIndex, string? TypeName)> globals, IReadOnlyCollection<ulong> indirectValues)
{
if (_created)
throw new InvalidOperationException("Context already created");
if (_globals != null)
throw new InvalidOperationException("Globals already set");
_globals = globals;
_indirectValues = indirectValues;
return this;
}

private MockMemorySpace.HeapFragment CreateContractDescriptor(int jsonLength, int pointerDataCount)
{
byte[] descriptor = new byte[ContractDescriptorHelpers.Size(TargetTestHelpers.Arch.Is64Bit)];
ContractDescriptorHelpers.Fill(descriptor, TargetTestHelpers.Arch, jsonLength, JsonDescriptorAddr, pointerDataCount, ContractPointerDataAddr);
return new MockMemorySpace.HeapFragment
{
Address = ContractDescriptorAddr,
Data = descriptor,
Name = "ContractDescriptor"
};
}

private string MakeContractsJson()
{
if (_contracts.Count == 0)
return string.Empty;
StringBuilder sb = new();
foreach (var c in _contracts)
{
sb.Append($"\"{c}\": 1,");
}
Debug.Assert(sb.Length > 0);
sb.Length--; // remove trailing comma
return sb.ToString();
}

private (MockMemorySpace.HeapFragment json, MockMemorySpace.HeapFragment pointerData) CreateDataDescriptor()
{
string metadataTypesJson = _types is not null ? ContractDescriptorHelpers.MakeTypesJson(_types) : string.Empty;
string metadataGlobalsJson = _globals is not null ? ContractDescriptorHelpers.MakeGlobalsJson(_globals) : string.Empty;
string interpolatedContracts = _contracts is not null ? MakeContractsJson() : string.Empty;
byte[] jsonBytes = Encoding.UTF8.GetBytes($$"""
{
"version": 0,
"baseline": "empty",
"contracts": { {{interpolatedContracts}} },
"types": { {{metadataTypesJson}} },
"globals": { {{metadataGlobalsJson}} }
}
""");
MockMemorySpace.HeapFragment json = new()
{
Address = JsonDescriptorAddr,
Data = jsonBytes,
Name = "JsonDescriptor"
};

MockMemorySpace.HeapFragment pointerData;
if (_indirectValues != null)
{
int pointerSize = TargetTestHelpers.PointerSize;
byte[] pointerDataBytes = new byte[_indirectValues.Count * pointerSize];
int offset = 0;
foreach (var value in _indirectValues)
{
TargetTestHelpers.WritePointer(pointerDataBytes.AsSpan(offset, pointerSize), value);
offset += pointerSize;
}
pointerData = new MockMemorySpace.HeapFragment
{
Address = ContractPointerDataAddr,
Data = pointerDataBytes,
Name = "PointerData"
};
}
else
{
pointerData = new MockMemorySpace.HeapFragment
{
Address = ContractPointerDataAddr,
Data = Array.Empty<byte>(),
Name = "PointerData"
};
}
return (json, pointerData);
}

private ulong CreateDescriptorFragments()
{
if (_created)
throw new InvalidOperationException("Context already created");

(var json, var pointerData) = CreateDataDescriptor();
int pointerDataCount = pointerData.Data is null ? 0 : pointerData.Data.Length / TargetTestHelpers.PointerSize;
MockMemorySpace.HeapFragment descriptor = CreateContractDescriptor(json.Data.Length, pointerDataCount);

AddHeapFragment(descriptor);
AddHeapFragment(json);
if (pointerData.Data.Length > 0)
AddHeapFragment(pointerData);

_created = true;
return descriptor.Address;
}

public bool TryCreateTarget([NotNullWhen(true)] out ContractDescriptorTarget? target)
{
if (_created)
throw new InvalidOperationException("Context already created");
ulong contractDescriptorAddress = CreateDescriptorFragments();
MockMemorySpace.ReadContext context = GetReadContext();
return ContractDescriptorTarget.TryCreate(contractDescriptorAddress, context.ReadFromTarget, out target);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// 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.Buffers.Binary;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;

namespace Microsoft.Diagnostics.DataContractReader.Tests.ContractDescriptor;

internal unsafe class ContractDescriptorHelpers
{
public static int Size(bool is64Bit) => is64Bit ? sizeof(ContractDescriptor64) : sizeof(ContractDescriptor32);

public static void Fill(Span<byte> dest, MockTarget.Architecture arch, int jsonDescriptorSize, uint jsonDescriptorAddr, int pointerDataCount, uint pointerDataAddr)
{
if (arch.Is64Bit)
{
ContractDescriptor64.Fill(dest, arch.IsLittleEndian, jsonDescriptorSize, jsonDescriptorAddr, pointerDataCount, pointerDataAddr);
}
else
{
ContractDescriptor32.Fill(dest, arch.IsLittleEndian, jsonDescriptorSize, jsonDescriptorAddr, pointerDataCount, pointerDataAddr);
}
}

private struct ContractDescriptor32
{
public ulong Magic = BitConverter.ToUInt64("DNCCDAC\0"u8);
public uint Flags = 0x2 /*32-bit*/ | 0x1;
public uint DescriptorSize;
public uint Descriptor;
public uint PointerDataCount;
public uint Pad0 = 0;
public uint PointerData;

public ContractDescriptor32() { }

public static void Fill(Span<byte> dest, bool isLittleEndian, int jsonDescriptorSize, uint jsonDescriptorAddr, int pointerDataCount, uint pointerDataAddr)
{
ContractDescriptor32 descriptor = new()
{
DescriptorSize = (uint)jsonDescriptorSize,
Descriptor = jsonDescriptorAddr,
PointerDataCount = (uint)pointerDataCount,
PointerData = pointerDataAddr,
};
if (BitConverter.IsLittleEndian != isLittleEndian)
descriptor.ReverseEndianness();

MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref descriptor, 1)).CopyTo(dest);
}

private void ReverseEndianness()
{
Magic = BinaryPrimitives.ReverseEndianness(Magic);
Flags = BinaryPrimitives.ReverseEndianness(Flags);
DescriptorSize = BinaryPrimitives.ReverseEndianness(DescriptorSize);
Descriptor = BinaryPrimitives.ReverseEndianness(Descriptor);
PointerDataCount = BinaryPrimitives.ReverseEndianness(PointerDataCount);
Pad0 = BinaryPrimitives.ReverseEndianness(Pad0);
PointerData = BinaryPrimitives.ReverseEndianness(PointerData);
}
}

private struct ContractDescriptor64
{
public ulong Magic = BitConverter.ToUInt64("DNCCDAC\0"u8);
public uint Flags = 0x1;
public uint DescriptorSize;
public ulong Descriptor;
public uint PointerDataCount;
public uint Pad0 = 0;
public ulong PointerData;

public ContractDescriptor64() { }

public static void Fill(Span<byte> dest, bool isLittleEndian, int jsonDescriptorSize, uint jsonDescriptorAddr, int pointerDataCount, uint pointerDataAddr)
{
ContractDescriptor64 descriptor = new()
{
DescriptorSize = (uint)jsonDescriptorSize,
Descriptor = jsonDescriptorAddr,
PointerDataCount = (uint)pointerDataCount,
PointerData = pointerDataAddr,
};
if (BitConverter.IsLittleEndian != isLittleEndian)
descriptor.ReverseEndianness();

MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref descriptor, 1)).CopyTo(dest);
}

private void ReverseEndianness()
{
Magic = BinaryPrimitives.ReverseEndianness(Magic);
Flags = BinaryPrimitives.ReverseEndianness(Flags);
DescriptorSize = BinaryPrimitives.ReverseEndianness(DescriptorSize);
Descriptor = BinaryPrimitives.ReverseEndianness(Descriptor);
PointerDataCount = BinaryPrimitives.ReverseEndianness(PointerDataCount);
Pad0 = BinaryPrimitives.ReverseEndianness(Pad0);
PointerData = BinaryPrimitives.ReverseEndianness(PointerData);
}
}

#region JSON formatting
private static string GetTypeJson(string name, Target.TypeInfo info)
{
string ret = string.Empty;
List<string> fields = info.Size is null ? [] : [$"\"!\":{info.Size}"];
fields.AddRange(info.Fields.Select(f => $"\"{f.Key}\":{(f.Value.TypeName is null ? f.Value.Offset : $"[{f.Value.Offset},\"{f.Value.TypeName}\"]")}"));
return $"\"{name}\":{{{string.Join(',', fields)}}}";
}

public static string MakeTypesJson(IDictionary<DataType, Target.TypeInfo> types)
{
return string.Join(',', types.Select(t => GetTypeJson(t.Key.ToString(), t.Value)));
}

public static string MakeGlobalsJson(IEnumerable<(string Name, ulong Value, string? Type)> globals)
{
return MakeGlobalsJson(globals.Select(g => (g.Name, (ulong?)g.Value, (uint?)null, g.Type)));
}

public static string MakeGlobalsJson(IEnumerable<(string Name, ulong? Value, uint? IndirectIndex, string? Type)> globals)
{
return string.Join(',', globals.Select(FormatGlobal));

static string FormatGlobal((string Name, ulong? Value, uint? IndirectIndex, string? Type) global)
{
if (global.Value is ulong value)
{
return $"\"{global.Name}\": {FormatValue(value, global.Type)}";
}
else if (global.IndirectIndex is uint index)
{
return $"\"{global.Name}\": {FormatIndirect(index, global.Type)}";
}
else
{
throw new InvalidOperationException("Global must have a value or indirect index");
}

}
static string FormatValue(ulong value, string? type)
{
return type is null ? $"{value}" : $"[{value},\"{type}\"]";
}
static string FormatIndirect(uint value, string? type)
{
return type is null ? $"[{value}]" : $"[[{value}],\"{type}\"]";
}
}

#endregion JSON formatting
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
using System.Text.Json;
using Xunit;

namespace Microsoft.Diagnostics.DataContractReader.UnitTests;
namespace Microsoft.Diagnostics.DataContractReader.Tests.ContractDescriptor;

public class ContractDescriptorParserTests
public class ParserTests
{
[Fact]
public void ParsesEmptyContract()
Expand Down
Loading

0 comments on commit 09b30a4

Please sign in to comment.