-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[cdac] Make contracts tests use mock/placeholder target instead of ac…
…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
1 parent
c31bb93
commit 09b30a4
Showing
32 changed files
with
989 additions
and
952 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
173 changes: 173 additions & 0 deletions
173
src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
156 changes: 156 additions & 0 deletions
156
src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorHelpers.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.