Skip to content

Commit bb87f5e

Browse files
test: add regression test for base model provider cycle recursion
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent c5cbe1c commit bb87f5e

1 file changed

Lines changed: 43 additions & 0 deletions

File tree

  • packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelProviderTests.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,49 @@ public BuildBaseTypeOverridingModelProvider(InputModelType inputModel, CSharpTyp
525525
protected override CSharpType? BuildBaseType() => _redirectedBaseType;
526526
}
527527

528+
// Regression: custom code (such as an inheritable system base model) can produce a base
529+
// ModelProvider chain that cycles back on itself. Base-model traversal during constructor,
530+
// field, and raw-data discovery must terminate instead of recursing infinitely.
531+
[Test]
532+
public void BaseModelProviderCycleDoesNotRecurseInfinitely()
533+
{
534+
var inputA = InputFactory.Model(
535+
"ModelA",
536+
usage: InputModelTypeUsage.Input | InputModelTypeUsage.Json | InputModelTypeUsage.Output,
537+
properties: [InputFactory.Property("aProp", InputPrimitiveType.String, isRequired: true)]);
538+
var inputB = InputFactory.Model(
539+
"ModelB",
540+
usage: InputModelTypeUsage.Input | InputModelTypeUsage.Json | InputModelTypeUsage.Output,
541+
properties: [InputFactory.Property("bProp", InputPrimitiveType.String, isRequired: true)]);
542+
MockHelpers.LoadMockGenerator(inputModelTypes: [inputA, inputB]);
543+
544+
var modelA = new CyclicBaseModelProvider(inputA);
545+
var modelB = new CyclicBaseModelProvider(inputB);
546+
547+
// Wire the base-model providers into a cycle: A -> B -> A.
548+
modelA.CyclicBase = modelB;
549+
modelB.CyclicBase = modelA;
550+
551+
Assert.AreSame(modelB, modelA.BaseModelProvider);
552+
Assert.AreSame(modelA, modelB.BaseModelProvider);
553+
554+
// Each of these walks the base-model chain and previously stack-overflowed on a cycle.
555+
Assert.DoesNotThrow(() => _ = modelA.FullConstructor);
556+
Assert.DoesNotThrow(() => _ = modelA.Constructors);
557+
Assert.DoesNotThrow(() => _ = modelA.Fields);
558+
}
559+
560+
private sealed class CyclicBaseModelProvider : ModelProvider
561+
{
562+
public CyclicBaseModelProvider(InputModelType inputModel) : base(inputModel)
563+
{
564+
}
565+
566+
public ModelProvider? CyclicBase { get; set; }
567+
568+
protected override ModelProvider? BuildBaseModelProvider() => CyclicBase;
569+
}
570+
528571
[Test]
529572
public void BuildModelAsStruct()
530573
{

0 commit comments

Comments
 (0)