Skip to content

cDAC test refactoring#126025

Draft
noahfalk wants to merge 2 commits intodotnet:mainfrom
noahfalk:builtincom-typedview-example
Draft

cDAC test refactoring#126025
noahfalk wants to merge 2 commits intodotnet:mainfrom
noahfalk:builtincom-typedview-example

Conversation

@noahfalk
Copy link
Copy Markdown
Member

This is an incomplete refactor but I wanted to show a slice of it for feedback before going to deep. I have two goals I am trying to get closer to:

  1. Make the cDAC tests easier to author/understand/maintain
  2. Extract out the core logic that mocks runtime data structures so that:
  • we could reuse it in tests outside the runtime repo (for example SOS)
  • generating the mock data doesn't depend on cDAC implementation specifics

In terms of those two goals:

Understandability

Today some of our tests do all their allocations and data writing in terms of low level byte buffer manipulation. Copilot however has no trouble producing strongly typed views over those buffers if you ask it to and I think it makes tests far easier to understand. As a simple comparison:

BEFORE:

        int P = helpers.PointerSize;
        var ccwFragment = new MockMemorySpace.HeapFragment
        {
            Name = "ComCallWrapper",
            Address = ccwAddr,
            Data = new byte[7 * P],
        };
        helpers.WritePointer(ccwFragment.Data.AsSpan(0, P), simpleWrapperAddr);
        helpers.WritePointer(ccwFragment.Data.AsSpan(6 * P, P), LinkedWrapperTerminator);
        builder.AddHeapFragment(ccwFragment);

AFTER:

        MockComCallWrapper wrapper = builtInCom.AddComCallWrapper();
        wrapper.SimpleWrapper = simpleWrapper.Address;
        wrapper.Next = LinkedWrapperTerminator;

That MockComCallWrapper is just a typed wrapper over a Memory<byte> using a Layout to understand where the fields are. When you set the properties SimpleWrapper/Next the property setters are doing the same work as WritePointer in the first example, just without all the obvious buffer manipulation. builtInCom.AddComCallWrapper() is also just giving a convenient API to allocate the HeapFragment, wrap it in the typed view object, and add the fragment to the builder. Tests still have full access to write raw HeapFragments if they want to, or the Memory is available from the typed view. I'm not trying to stop tests from dropping into the weeds when its useful. TypedViews also aren't compelled to have any specific set of accessors on them - tests can make them as rich or lean as they want. So far I've just been having copilot add accessors for the fields that tests interacted with.

Extracting out runtime data mocking logic

Continuing to use BuiltInComTests as an example, the previous file was a mixture of layout definitions, global definitions, target creation, data structure allocation, and the unit tests themselves. A little more subtle, the test's definition of MockDescriptors.TypeFields has a cDAC DataType field inside of it which forces a cDAC library dependency in order to generate the data. Similarly creating the actual layout offsets produces TypeInfo objects which are cDAC abtractions as well. In the factored version MockBuiltInComBuilder.cs has all the type information, typed wrappers, and allocation helpers that could be reused for mocking any runtime COM data structures without any cDAC assembly dependencies. I created a simple Layout/LayoutField abstraction that doesn't have that coupling. Calling CreateTypeInfo converts a Layout into a Target.TypeInfo. Not included in this PR but I confirmed its also possible to serialize the Layouts into a contract descriptor JSON blob inside a HeapFragment and then point cDAC at that memory if we want to.

My proposal is to continue doing this refactor through the remainder of the cDAC unit tests (I can deal with it) and then we'll be in a better shape for the split that Juan's PR was trying to do. The refactor shouldn't need to touch any of the product code and its easy to verify all the tests pass at any point along the way. Depending on how folks would like to review it I could either prepare one big PR that does it in one shot or I could incrementally convert a portion of the tests at a time, similar to this PR.

But before I do anything more - thoughts on the direction?

@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag
See info in area-owners.md if you want to be subscribed.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new “typed view” abstraction for authoring cDAC tests by modeling mock target-memory structures as strongly-typed wrappers over Memory<byte>, aiming to make tests easier to read and to decouple mock-structure construction from cDAC implementation specifics.

Changes:

  • Added Layout/TypedView infrastructure for defining field layouts and reading/writing fields without manual span slicing.
  • Added MockBuiltInComBuilder and typed wrappers for COM-related runtime structures used by BuiltInCOMTests.
  • Refactored BuiltInCOMTests to allocate/populate mock COM structures via typed wrappers and to create contract type info/globals from layouts.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/native/managed/cdac/tests/TypedView.cs New base class for typed wrappers over mock memory with endian/pointer-size aware field access.
src/native/managed/cdac/tests/Layout.cs New layout and layout-builder abstractions for describing mock structure shapes.
src/native/managed/cdac/tests/MockBuiltInComBuilder.cs New COM-specific typed wrappers and allocation helpers used by COM contract tests.
src/native/managed/cdac/tests/TargetTestHelpers.cs Adds CreateTypeInfo(Layout) adapter to convert layouts into Target.TypeInfo.
src/native/managed/cdac/tests/BuiltInCOMTests.cs Refactors tests to use typed wrappers + builder and generates globals/types from layouts.
src/native/managed/cdac/tests/README.md Documents the new TypedView/Layout pattern and when to prefer it in tests.

Comment on lines +245 to +250
fields[field.Name] = new Target.FieldInfo
{
Offset = field.Offset,
Type = DataType.Unknown,
};
}
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateTypeInfo sets every Target.FieldInfo.Type to DataType.Unknown. That makes the resulting TypeInfo unusable with helpers like SizeOfTypeInfo (which throws for non-primitive types) and may limit reuse of these TypeInfos elsewhere in tests. Consider inferring a primitive type from LayoutField.Size (or extending LayoutField to carry a lightweight kind) so emitted FieldInfo.Type is at least a usable primitive.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, it would be good to pass through the proper type info here.

Copy link
Copy Markdown
Member Author

@noahfalk noahfalk Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We certainly could provide it but I want to poke at this a bit. My understanding is that the official data descriptor provided by the runtime doesn't provide this type information and thus far every test I've looked at passed without providing it. I was a little worried that if we provide the data we might be giving cDAC a crutch where it will assume the type information is available and then fail against a real target which doesn't provide equally rich type information.

SizeOfTypeInfo looked similarly suspicious where the test is making a size computation by summing the field sizes but the production code probably can't rely on being able to do the same thing.

Copy link
Copy Markdown
Member

@max-charlamb max-charlamb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy with this change. It will make unit tests easier to write and understand.

It may be nice to put the test infrastructure in a sub-folder.

Comment on lines +245 to +250
fields[field.Name] = new Target.FieldInfo
{
Offset = field.Offset,
Type = DataType.Unknown,
};
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, it would be good to pass through the proper type info here.

noahfalk and others added 2 commits March 25, 2026 01:49
Refactor BuiltInCOMTests to use typed Layout/TypedView wrappers and a focused MockBuiltInComBuilder instead of hand-written mock memory setup. The tests now model runtime shapes more directly, including ComMethodTable trailing vtables, StdInterfaceDesc standard-interface vtables, and RCW inline interface-entry storage.

Also document the TypedView pattern in the cDAC test README and align the RCW mock layout and helpers with the runtime's descriptor definitions so the sample better demonstrates the intended refactoring direction.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@noahfalk noahfalk force-pushed the builtincom-typedview-example branch from d3e7a97 to 35141e0 Compare March 25, 2026 11:22
@noahfalk
Copy link
Copy Markdown
Member Author

I rebased to fix merge issues, updated the PR based on changes in the underlying COM tests recently, and addressed most of the feedback. One issue that is still outstanding is the field type data which I can happily do but I wanted to the conversation to close first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants