Skip to content

Commit 09b30a4

Browse files
authored
[cdac] Make contracts tests use mock/placeholder target instead of actual 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
1 parent c31bb93 commit 09b30a4

32 files changed

+989
-952
lines changed

src/native/managed/cdacreader/tests/CodeVersionsTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
using Moq;
88
using Xunit;
99

10-
namespace Microsoft.Diagnostics.DataContractReader.UnitTests;
10+
namespace Microsoft.Diagnostics.DataContractReader.Tests;
1111

1212
using MockCodeVersions = MockDescriptors.CodeVersions;
1313

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Diagnostics.CodeAnalysis;
7+
using System.Diagnostics;
8+
using System.Linq;
9+
using System.Text;
10+
11+
namespace Microsoft.Diagnostics.DataContractReader.Tests.ContractDescriptor;
12+
13+
internal class ContractDescriptorBuilder : MockMemorySpace.Builder
14+
{
15+
// These addresses are arbitrary and are used to store the contract descriptor components.
16+
// They should not overlap with any other heap fragment addresses.
17+
private const ulong ContractDescriptorAddr = 0xaaaaaaaa;
18+
private const uint JsonDescriptorAddr = 0xdddddddd;
19+
private const uint ContractPointerDataAddr = 0xeeeeeeee;
20+
21+
private bool _created = false;
22+
23+
private IReadOnlyCollection<string> _contracts;
24+
private IDictionary<DataType, Target.TypeInfo> _types;
25+
private IReadOnlyCollection<(string Name, ulong? Value, uint? IndirectIndex, string? TypeName)> _globals;
26+
private IReadOnlyCollection<ulong> _indirectValues;
27+
28+
public ContractDescriptorBuilder(TargetTestHelpers targetTestHelpers)
29+
: base(targetTestHelpers)
30+
{ }
31+
32+
public ContractDescriptorBuilder SetContracts(IReadOnlyCollection<string> contracts)
33+
{
34+
if (_created)
35+
throw new InvalidOperationException("Context already created");
36+
_contracts = contracts;
37+
return this;
38+
}
39+
40+
public ContractDescriptorBuilder SetTypes(IDictionary<DataType, Target.TypeInfo> types)
41+
{
42+
if (_created)
43+
throw new InvalidOperationException("Context already created");
44+
_types = types;
45+
return this;
46+
}
47+
48+
public ContractDescriptorBuilder SetGlobals(IReadOnlyCollection<(string Name, ulong Value, string? TypeName)> globals)
49+
{
50+
if (_created)
51+
throw new InvalidOperationException("Context already created");
52+
if (_globals != null)
53+
throw new InvalidOperationException("Globals already set");
54+
_globals = globals.Select(g => (g.Name, (ulong?)g.Value, (uint?)null, g.TypeName)).ToArray();
55+
_indirectValues = null;
56+
return this;
57+
}
58+
59+
public ContractDescriptorBuilder SetGlobals(IReadOnlyCollection<(string Name, ulong? Value, uint? IndirectIndex, string? TypeName)> globals, IReadOnlyCollection<ulong> indirectValues)
60+
{
61+
if (_created)
62+
throw new InvalidOperationException("Context already created");
63+
if (_globals != null)
64+
throw new InvalidOperationException("Globals already set");
65+
_globals = globals;
66+
_indirectValues = indirectValues;
67+
return this;
68+
}
69+
70+
private MockMemorySpace.HeapFragment CreateContractDescriptor(int jsonLength, int pointerDataCount)
71+
{
72+
byte[] descriptor = new byte[ContractDescriptorHelpers.Size(TargetTestHelpers.Arch.Is64Bit)];
73+
ContractDescriptorHelpers.Fill(descriptor, TargetTestHelpers.Arch, jsonLength, JsonDescriptorAddr, pointerDataCount, ContractPointerDataAddr);
74+
return new MockMemorySpace.HeapFragment
75+
{
76+
Address = ContractDescriptorAddr,
77+
Data = descriptor,
78+
Name = "ContractDescriptor"
79+
};
80+
}
81+
82+
private string MakeContractsJson()
83+
{
84+
if (_contracts.Count == 0)
85+
return string.Empty;
86+
StringBuilder sb = new();
87+
foreach (var c in _contracts)
88+
{
89+
sb.Append($"\"{c}\": 1,");
90+
}
91+
Debug.Assert(sb.Length > 0);
92+
sb.Length--; // remove trailing comma
93+
return sb.ToString();
94+
}
95+
96+
private (MockMemorySpace.HeapFragment json, MockMemorySpace.HeapFragment pointerData) CreateDataDescriptor()
97+
{
98+
string metadataTypesJson = _types is not null ? ContractDescriptorHelpers.MakeTypesJson(_types) : string.Empty;
99+
string metadataGlobalsJson = _globals is not null ? ContractDescriptorHelpers.MakeGlobalsJson(_globals) : string.Empty;
100+
string interpolatedContracts = _contracts is not null ? MakeContractsJson() : string.Empty;
101+
byte[] jsonBytes = Encoding.UTF8.GetBytes($$"""
102+
{
103+
"version": 0,
104+
"baseline": "empty",
105+
"contracts": { {{interpolatedContracts}} },
106+
"types": { {{metadataTypesJson}} },
107+
"globals": { {{metadataGlobalsJson}} }
108+
}
109+
""");
110+
MockMemorySpace.HeapFragment json = new()
111+
{
112+
Address = JsonDescriptorAddr,
113+
Data = jsonBytes,
114+
Name = "JsonDescriptor"
115+
};
116+
117+
MockMemorySpace.HeapFragment pointerData;
118+
if (_indirectValues != null)
119+
{
120+
int pointerSize = TargetTestHelpers.PointerSize;
121+
byte[] pointerDataBytes = new byte[_indirectValues.Count * pointerSize];
122+
int offset = 0;
123+
foreach (var value in _indirectValues)
124+
{
125+
TargetTestHelpers.WritePointer(pointerDataBytes.AsSpan(offset, pointerSize), value);
126+
offset += pointerSize;
127+
}
128+
pointerData = new MockMemorySpace.HeapFragment
129+
{
130+
Address = ContractPointerDataAddr,
131+
Data = pointerDataBytes,
132+
Name = "PointerData"
133+
};
134+
}
135+
else
136+
{
137+
pointerData = new MockMemorySpace.HeapFragment
138+
{
139+
Address = ContractPointerDataAddr,
140+
Data = Array.Empty<byte>(),
141+
Name = "PointerData"
142+
};
143+
}
144+
return (json, pointerData);
145+
}
146+
147+
private ulong CreateDescriptorFragments()
148+
{
149+
if (_created)
150+
throw new InvalidOperationException("Context already created");
151+
152+
(var json, var pointerData) = CreateDataDescriptor();
153+
int pointerDataCount = pointerData.Data is null ? 0 : pointerData.Data.Length / TargetTestHelpers.PointerSize;
154+
MockMemorySpace.HeapFragment descriptor = CreateContractDescriptor(json.Data.Length, pointerDataCount);
155+
156+
AddHeapFragment(descriptor);
157+
AddHeapFragment(json);
158+
if (pointerData.Data.Length > 0)
159+
AddHeapFragment(pointerData);
160+
161+
_created = true;
162+
return descriptor.Address;
163+
}
164+
165+
public bool TryCreateTarget([NotNullWhen(true)] out ContractDescriptorTarget? target)
166+
{
167+
if (_created)
168+
throw new InvalidOperationException("Context already created");
169+
ulong contractDescriptorAddress = CreateDescriptorFragments();
170+
MockMemorySpace.ReadContext context = GetReadContext();
171+
return ContractDescriptorTarget.TryCreate(contractDescriptorAddress, context.ReadFromTarget, out target);
172+
}
173+
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Buffers.Binary;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using System.Runtime.InteropServices;
9+
10+
namespace Microsoft.Diagnostics.DataContractReader.Tests.ContractDescriptor;
11+
12+
internal unsafe class ContractDescriptorHelpers
13+
{
14+
public static int Size(bool is64Bit) => is64Bit ? sizeof(ContractDescriptor64) : sizeof(ContractDescriptor32);
15+
16+
public static void Fill(Span<byte> dest, MockTarget.Architecture arch, int jsonDescriptorSize, uint jsonDescriptorAddr, int pointerDataCount, uint pointerDataAddr)
17+
{
18+
if (arch.Is64Bit)
19+
{
20+
ContractDescriptor64.Fill(dest, arch.IsLittleEndian, jsonDescriptorSize, jsonDescriptorAddr, pointerDataCount, pointerDataAddr);
21+
}
22+
else
23+
{
24+
ContractDescriptor32.Fill(dest, arch.IsLittleEndian, jsonDescriptorSize, jsonDescriptorAddr, pointerDataCount, pointerDataAddr);
25+
}
26+
}
27+
28+
private struct ContractDescriptor32
29+
{
30+
public ulong Magic = BitConverter.ToUInt64("DNCCDAC\0"u8);
31+
public uint Flags = 0x2 /*32-bit*/ | 0x1;
32+
public uint DescriptorSize;
33+
public uint Descriptor;
34+
public uint PointerDataCount;
35+
public uint Pad0 = 0;
36+
public uint PointerData;
37+
38+
public ContractDescriptor32() { }
39+
40+
public static void Fill(Span<byte> dest, bool isLittleEndian, int jsonDescriptorSize, uint jsonDescriptorAddr, int pointerDataCount, uint pointerDataAddr)
41+
{
42+
ContractDescriptor32 descriptor = new()
43+
{
44+
DescriptorSize = (uint)jsonDescriptorSize,
45+
Descriptor = jsonDescriptorAddr,
46+
PointerDataCount = (uint)pointerDataCount,
47+
PointerData = pointerDataAddr,
48+
};
49+
if (BitConverter.IsLittleEndian != isLittleEndian)
50+
descriptor.ReverseEndianness();
51+
52+
MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref descriptor, 1)).CopyTo(dest);
53+
}
54+
55+
private void ReverseEndianness()
56+
{
57+
Magic = BinaryPrimitives.ReverseEndianness(Magic);
58+
Flags = BinaryPrimitives.ReverseEndianness(Flags);
59+
DescriptorSize = BinaryPrimitives.ReverseEndianness(DescriptorSize);
60+
Descriptor = BinaryPrimitives.ReverseEndianness(Descriptor);
61+
PointerDataCount = BinaryPrimitives.ReverseEndianness(PointerDataCount);
62+
Pad0 = BinaryPrimitives.ReverseEndianness(Pad0);
63+
PointerData = BinaryPrimitives.ReverseEndianness(PointerData);
64+
}
65+
}
66+
67+
private struct ContractDescriptor64
68+
{
69+
public ulong Magic = BitConverter.ToUInt64("DNCCDAC\0"u8);
70+
public uint Flags = 0x1;
71+
public uint DescriptorSize;
72+
public ulong Descriptor;
73+
public uint PointerDataCount;
74+
public uint Pad0 = 0;
75+
public ulong PointerData;
76+
77+
public ContractDescriptor64() { }
78+
79+
public static void Fill(Span<byte> dest, bool isLittleEndian, int jsonDescriptorSize, uint jsonDescriptorAddr, int pointerDataCount, uint pointerDataAddr)
80+
{
81+
ContractDescriptor64 descriptor = new()
82+
{
83+
DescriptorSize = (uint)jsonDescriptorSize,
84+
Descriptor = jsonDescriptorAddr,
85+
PointerDataCount = (uint)pointerDataCount,
86+
PointerData = pointerDataAddr,
87+
};
88+
if (BitConverter.IsLittleEndian != isLittleEndian)
89+
descriptor.ReverseEndianness();
90+
91+
MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref descriptor, 1)).CopyTo(dest);
92+
}
93+
94+
private void ReverseEndianness()
95+
{
96+
Magic = BinaryPrimitives.ReverseEndianness(Magic);
97+
Flags = BinaryPrimitives.ReverseEndianness(Flags);
98+
DescriptorSize = BinaryPrimitives.ReverseEndianness(DescriptorSize);
99+
Descriptor = BinaryPrimitives.ReverseEndianness(Descriptor);
100+
PointerDataCount = BinaryPrimitives.ReverseEndianness(PointerDataCount);
101+
Pad0 = BinaryPrimitives.ReverseEndianness(Pad0);
102+
PointerData = BinaryPrimitives.ReverseEndianness(PointerData);
103+
}
104+
}
105+
106+
#region JSON formatting
107+
private static string GetTypeJson(string name, Target.TypeInfo info)
108+
{
109+
string ret = string.Empty;
110+
List<string> fields = info.Size is null ? [] : [$"\"!\":{info.Size}"];
111+
fields.AddRange(info.Fields.Select(f => $"\"{f.Key}\":{(f.Value.TypeName is null ? f.Value.Offset : $"[{f.Value.Offset},\"{f.Value.TypeName}\"]")}"));
112+
return $"\"{name}\":{{{string.Join(',', fields)}}}";
113+
}
114+
115+
public static string MakeTypesJson(IDictionary<DataType, Target.TypeInfo> types)
116+
{
117+
return string.Join(',', types.Select(t => GetTypeJson(t.Key.ToString(), t.Value)));
118+
}
119+
120+
public static string MakeGlobalsJson(IEnumerable<(string Name, ulong Value, string? Type)> globals)
121+
{
122+
return MakeGlobalsJson(globals.Select(g => (g.Name, (ulong?)g.Value, (uint?)null, g.Type)));
123+
}
124+
125+
public static string MakeGlobalsJson(IEnumerable<(string Name, ulong? Value, uint? IndirectIndex, string? Type)> globals)
126+
{
127+
return string.Join(',', globals.Select(FormatGlobal));
128+
129+
static string FormatGlobal((string Name, ulong? Value, uint? IndirectIndex, string? Type) global)
130+
{
131+
if (global.Value is ulong value)
132+
{
133+
return $"\"{global.Name}\": {FormatValue(value, global.Type)}";
134+
}
135+
else if (global.IndirectIndex is uint index)
136+
{
137+
return $"\"{global.Name}\": {FormatIndirect(index, global.Type)}";
138+
}
139+
else
140+
{
141+
throw new InvalidOperationException("Global must have a value or indirect index");
142+
}
143+
144+
}
145+
static string FormatValue(ulong value, string? type)
146+
{
147+
return type is null ? $"{value}" : $"[{value},\"{type}\"]";
148+
}
149+
static string FormatIndirect(uint value, string? type)
150+
{
151+
return type is null ? $"[{value}]" : $"[[{value}],\"{type}\"]";
152+
}
153+
}
154+
155+
#endregion JSON formatting
156+
}

src/native/managed/cdacreader/tests/ContractDescriptorParserTests.cs renamed to src/native/managed/cdacreader/tests/ContractDescriptor/ParserTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
using System.Text.Json;
66
using Xunit;
77

8-
namespace Microsoft.Diagnostics.DataContractReader.UnitTests;
8+
namespace Microsoft.Diagnostics.DataContractReader.Tests.ContractDescriptor;
99

10-
public class ContractDescriptorParserTests
10+
public class ParserTests
1111
{
1212
[Fact]
1313
public void ParsesEmptyContract()

0 commit comments

Comments
 (0)