@@ -895,6 +895,89 @@ def test_method(self) -> str:
895895 assert '99' in plugin3 .instance_code
896896
897897
898+ class TestMetaclassCallGuard :
899+ """Test suite for metaclass __call__ guard for base/non-instrumented classes.
900+
901+ This addresses the issue where D3PluginClientMeta.__new__ skips instrumentation
902+ when name == "D3PluginClient", so the base class never gets filtered_init_args/
903+ instance_code. However, __call__ unconditionally reads cls.filtered_init_args,
904+ causing AttributeError when instantiating D3PluginClient() directly or any
905+ non-instrumented class.
906+ """
907+
908+ def test_base_class_instantiation_without_args (self ):
909+ """Test that base D3PluginClient can be instantiated without arguments.
910+
911+ This would raise AttributeError without the guard in __call__.
912+ """
913+ # This should not raise AttributeError
914+ plugin = D3PluginClient ()
915+
916+ # Verify the instance is created properly
917+ assert isinstance (plugin , D3PluginClient )
918+ assert hasattr (plugin , '_hostname' )
919+ assert hasattr (plugin , '_port' )
920+ assert hasattr (plugin , '_override_module_name' )
921+
922+ def test_base_class_does_not_have_instrumentation_attributes (self ):
923+ """Test that base D3PluginClient class lacks instrumentation attributes."""
924+ # The base class should not have these attributes
925+ assert not hasattr (D3PluginClient , 'filtered_init_args' )
926+ assert not hasattr (D3PluginClient , 'source_code' )
927+ assert not hasattr (D3PluginClient , 'source_code_py27' )
928+
929+ def test_instrumented_subclass_has_attributes (self ):
930+ """Test that instrumented subclasses do have instrumentation attributes."""
931+ class InstrumentedPlugin (D3PluginClient ):
932+ def __init__ (self , a : int ):
933+ super ().__init__ ()
934+ self .a = a
935+
936+ def test_method (self ) -> int :
937+ return self .a
938+
939+ # Instrumented subclasses should have these attributes
940+ assert hasattr (InstrumentedPlugin , 'filtered_init_args' )
941+ assert hasattr (InstrumentedPlugin , 'source_code' )
942+ assert hasattr (InstrumentedPlugin , 'source_code_py27' )
943+
944+ def test_instrumented_subclass_instantiation_works (self ):
945+ """Test that instrumented subclasses can still be instantiated normally."""
946+ class InstrumentedPlugin (D3PluginClient ):
947+ def __init__ (self , a : int , b : str = "default" ):
948+ super ().__init__ ()
949+ self .a = a
950+ self .b = b
951+
952+ def test_method (self ) -> str :
953+ return f"{ self .a } :{ self .b } "
954+
955+ # This should work as before
956+ plugin = InstrumentedPlugin (42 , "test" )
957+
958+ assert isinstance (plugin , InstrumentedPlugin )
959+ assert plugin .a == 42
960+ assert plugin .b == "test"
961+
962+ # Should have instance_code generated
963+ assert hasattr (plugin , 'instance_code' )
964+ assert 'InstrumentedPlugin' in plugin .instance_code
965+ assert '42' in plugin .instance_code
966+
967+ def test_base_class_instance_no_instance_code (self ):
968+ """Test that base D3PluginClient instance doesn't get instance_code.
969+
970+ Since the base class bypasses instrumentation, instances shouldn't
971+ have instance_code attribute (or it should be handled gracefully).
972+ """
973+ plugin = D3PluginClient ()
974+
975+ # Base class instances shouldn't have instance_code
976+ # (or if they do, it should be handled gracefully)
977+ # The important thing is no AttributeError during instantiation
978+ assert isinstance (plugin , D3PluginClient )
979+
980+
898981class TestInitWrapperSuperCall :
899982 """Test suite for automatic parent __init__ calling when user forgets super().__init__()."""
900983
0 commit comments