soenneker / soenneker.utils.autobogus

The .NET Autogenerator
https://soenneker.com
MIT License
39 stars 4 forks source link

Generate fake from proto (gRPC) - class causes a fatal error / crashes the whole test runner process #190

Closed rizi closed 6 months ago

rizi commented 6 months ago

When trying to create a fake from a generated class (gRPC object), the following error (crashes the whole test runner process) occurs:

Exit code is -1073741819 (Fatal error. Internal CLR error. (0x80131506)

Exit code is -1073741819 (Fatal error. Internal CLR error. (0x80131506)
   at System.Delegate.DelegateConstruct(System.Object, IntPtr)
   at System.RuntimeMethodHandle.InvokeMethod(System.Object, Void**, System.Signature, Boolean)
   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(System.Object, System.Span`1<System.Object>, System.Reflection.BindingFlags)
   at System.Reflection.MethodBaseInvoker.InvokeWithFewArgs(System.Object, System.Reflection.BindingFlags, System.Reflection.Binder, System.Object[], System.Globalization.CultureInfo)
   at System.Reflection.RuntimeConstructorInfo.Invoke(System.Reflection.BindingFlags, System.Reflection.Binder, System.Object[], System.Globalization.CultureInfo)
   at System.Reflection.ConstructorInfo.Invoke(System.Object[])
   at Soenneker.Reflection.Cache.Constructors.CachedConstructor.Invoke(System.Object[])
   at Soenneker.Utils.AutoBogus.AutoFakerBinder.CreateInstance[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](Soenneker.Utils.AutoBogus.Context.AutoFakerContext, Soenneker.Reflection.Cache.Types.CachedType)
   at Soenneker.Utils.AutoBogus.Generators.Types.TypeGenerator`1[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].Soenneker.Utils.AutoBogus.Generators.Abstract.IAutoFakerGenerator.Generate(Soenneker.Utils.AutoBogus.Context.AutoFakerContext)
   at Soenneker.Utils.AutoBogus.AutoFakerBinder.CreateInstance[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](Soenneker.Utils.AutoBogus.Context.AutoFakerContext, Soenneker.Reflection.Cache.Types.CachedType)
   at Soenneker.Utils.AutoBogus.Generators.Types.TypeGenerator`1[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].Soenneker.Utils.AutoBogus.Generators.Abstract.IAutoFakerGenerator.Generate(Soenneker.Utils.AutoBogus.Context.AutoFakerContext)
   at Soenneker.Utils.AutoBogus.AutoFakerBinder.PopulateMembers(System.Object, Soenneker.Utils.AutoBogus.Context.AutoFakerContext, Soenneker.Reflection.Cache.Types.CachedType, System.Collections.Generic.List`1<Soenneker.Utils.AutoBogus.AutoMember>)
   at Soenneker.Utils.AutoBogus.AutoFaker`1+<>c__DisplayClass19_0[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].<PrepareFinish>b__0(Bogus.Faker, System.__Canon)
   at Bogus.Faker`1[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].PopulateInternal(System.__Canon, System.String[])
   at Bogus.Faker`1[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].Generate(System.String)
   at Soenneker.Utils.AutoBogus.AutoFaker`1[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].Generate(System.String)
   at Bogus.Faker`1[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].op_Implicit(Bogus.Faker`1<System.__Canon>)

Here is the sample class which is generated by the C#-Protobuf compiler:

#nullable disable
namespace Google.Protobuf
{
  /// <summary>
  /// Generic interface for a Protocol Buffers message,
  /// where the type parameter is expected to be the same type as
  /// the implementation class.
  /// </summary>
  /// <typeparam name="T">The message type.</typeparam>
  public interface IMessage<T> : IMessage, IEquatable<T>, IDeepCloneable<T> where T : IMessage<T>
  {
    /// <summary>Merges the given message into this one.</summary>
    /// <remarks>See the user guide for precise merge semantics.</remarks>
    /// <param name="message">The message to merge with this one. Must not be null.</param>
    void MergeFrom(T message);
  }
}

 public sealed partial class ArticleCulture : pb::IMessage<ArticleCulture>
 #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
     , pb::IBufferMessage
 #endif
 {
   private static readonly pb::MessageParser<ArticleCulture> _parser = new pb::MessageParser<ArticleCulture>(() => new ArticleCulture());
   private pb::UnknownFieldSet _unknownFields;
   [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
   [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
   public static pb::MessageParser<ArticleCulture> Parser { get { return _parser; } }

   [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
   [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
   public static pbr::MessageDescriptor Descriptor {
     get { return global::Upper.ArticleConnector.Grpc.Contract.Lutz.ArticleCultureServiceReflection.Descriptor.MessageTypes[1]; }
   }

   [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
   [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
   pbr::MessageDescriptor pb::IMessage.Descriptor {
     get { return Descriptor; }
   }

   [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
   [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
   public ArticleCulture() {
     OnConstruction();
   }

   partial void OnConstruction();

   [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
   [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
   public ArticleCulture(ArticleCulture other) : this() {
     id_ = other.id_;
     name_ = other.name_;
     _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
   }

   [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
   [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
   public ArticleCulture Clone() {
     return new ArticleCulture(this);
   }

   /// <summary>Field number for the "id" field.</summary>
   public const int IdFieldNumber = 1;
   private string id_ = "";
   [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
   [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
   public string Id {
     get { return id_; }
     set {
       id_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
     }
   }

   /// <summary>Field number for the "name" field.</summary>
   public const int NameFieldNumber = 2;
   private string name_ = "";
   [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
   [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
   public string Name {
     get { return name_; }
     set {
       name_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
     }
   }

   [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
   [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
   public override bool Equals(object other) {
     return Equals(other as ArticleCulture);
   }

   [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
   [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
   public bool Equals(ArticleCulture other) {
     if (ReferenceEquals(other, null)) {
       return false;
     }
     if (ReferenceEquals(other, this)) {
       return true;
     }
     if (Id != other.Id) return false;
     if (Name != other.Name) return false;
     return Equals(_unknownFields, other._unknownFields);
   }

   [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
   [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
   public override int GetHashCode() {
     int hash = 1;
     if (Id.Length != 0) hash ^= Id.GetHashCode();
     if (Name.Length != 0) hash ^= Name.GetHashCode();
     if (_unknownFields != null) {
       hash ^= _unknownFields.GetHashCode();
     }
     return hash;
   }

   [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
   [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
   public override string ToString() {
     return pb::JsonFormatter.ToDiagnosticString(this);
   }

   [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
   [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
   public void WriteTo(pb::CodedOutputStream output) {
   #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
     output.WriteRawMessage(this);
   #else
     if (Id.Length != 0) {
       output.WriteRawTag(10);
       output.WriteString(Id);
     }
     if (Name.Length != 0) {
       output.WriteRawTag(18);
       output.WriteString(Name);
     }
     if (_unknownFields != null) {
       _unknownFields.WriteTo(output);
     }
   #endif
   }

   #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
   [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
   [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
   void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
     if (Id.Length != 0) {
       output.WriteRawTag(10);
       output.WriteString(Id);
     }
     if (Name.Length != 0) {
       output.WriteRawTag(18);
       output.WriteString(Name);
     }
     if (_unknownFields != null) {
       _unknownFields.WriteTo(ref output);
     }
   }
   #endif

   [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
   [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
   public int CalculateSize() {
     int size = 0;
     if (Id.Length != 0) {
       size += 1 + pb::CodedOutputStream.ComputeStringSize(Id);
     }
     if (Name.Length != 0) {
       size += 1 + pb::CodedOutputStream.ComputeStringSize(Name);
     }
     if (_unknownFields != null) {
       size += _unknownFields.CalculateSize();
     }
     return size;
   }

   [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
   [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
   public void MergeFrom(ArticleCulture other) {
     if (other == null) {
       return;
     }
     if (other.Id.Length != 0) {
       Id = other.Id;
     }
     if (other.Name.Length != 0) {
       Name = other.Name;
     }
     _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
   }

   [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
   [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
   public void MergeFrom(pb::CodedInputStream input) {
   #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
     input.ReadRawMessage(this);
   #else
     uint tag;
     while ((tag = input.ReadTag()) != 0) {
       switch(tag) {
         default:
           _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
           break;
         case 10: {
           Id = input.ReadString();
           break;
         }
         case 18: {
           Name = input.ReadString();
           break;
         }
       }
     }
   #endif
   }

   #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
   [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
   [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
   void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
     uint tag;
     while ((tag = input.ReadTag()) != 0) {
       switch(tag) {
         default:
           _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
           break;
         case 10: {
           Id = input.ReadString();
           break;
         }
         case 18: {
           Name = input.ReadString();
           break;
         }
       }
     }
   }
   #endif
 }

Here is the code, that causes the error:

 ArticleCulture articleCulture = new AutoFaker<ArticleCulture>();
soenneker commented 6 months ago

Without actually troubleshooting, I'm betting the parameterless constructor is being chosen for the creation point.

In that case OnConstruction() is being called which is a partial, and it's failing.

I wonder if the best solution is to fail gracefully and return null for this object (because the object can't be created) or maybe try the next constructor or something.. any other ideas?

rizi commented 6 months ago

Without actually troubleshooting, I'm betting the parameterless constructor is being chosen for the creation point.

In that case OnConstruction() is being called which is a partial, and it's failing.

I wonder if the best solution is to fail gracefully and return null for this object (because the object can't be created) or maybe try the next constructor or something.. any other ideas?

I digged a little bit deeper into the problem and it's definitely not the partial method call, it's the MessageParser, it has two constructors and I think the Func causes this issue.

public MessageParser(Func<T> factory)
  : this(factory, false, (ExtensionRegistry) null)
{
}

internal MessageParser(
  Func<T> factory,
  bool discardUnknownFields,
  ExtensionRegistry extensions)
  : base((Func<IMessage>) (() => (IMessage) factory()), discardUnknownFields, extensions)
{
  this.factory = factory;
}

image

I don't know if it's possible to fake such an object, would be quite helpful though.

If it's not possible I would (as you suggested) fail gracefully and return null.

What do you think?

Br

soenneker commented 6 months ago

Can you look at this commit: https://github.com/soenneker/soenneker.utils.autobogus/commit/4c4b80e9d33927056221a9bb0aa792883cdf8a0d

To boil the change down: if a constructor has a Func as a parameter, it is excluded from generation.

I think you should be able to still generate the parent ArticleCulture now at least... let me know how it works

rizi commented 6 months ago

@soenneker it's working with the latest version! (https://github.com/soenneker/soenneker.utils.autobogus/commit/4c4b80e9d33927056221a9bb0aa792883cdf8a0d) Thank you very much.