2015-06-30 16 views
5

का उपयोग कर एक Gradle प्लगइन का परीक्षण के रूप में, मैं एक ग्रूवी विधि बाहर ठूंठ करना चाहते हैं। यह पुष्टि करने के लिए है कि यह सही कमांड लाइन कॉल कर रहा है।नकली Gradle project.exec {...} metaClass

Project proj = ProjectBuilder.builder().build() 

proj.metaClass.exec = { Closure obj -> 
    println 'MOCK EXEC' 
} 

proj.exec { 
    executable 'echo' 
    args 'PROJECT EXEC' 
} 
// prints 'PROJECT EXEC' instead of the 'MOCK EXEC' I expected 

क्या उत्सुक है कि अगर मैं othername करने के लिए दोनों exec तरीकों का नाम बदलने के लिए, तो इसे सही ढंग से काम करता है:: मैं इस का उपयोग कर metaprogramming प्रयास किया गया था

Project proj = ProjectBuilder.builder().build() 

proj.metaClass.othername = { Closure obj -> 
    println 'MOCK EXEC' 
} 

proj.othername { 
    executable 'echo' 
    args 'PROJECT EXEC' 
} 
// prints 'MOCK EXEC' as expected 

मैं यह पता लगाने की कोशिश कर रहा हूँ क्यों मौजूदा project.exec विधि मेटाप्रोग्रामिंग विफल होने का कारण बनती है और यदि कोई कामकाज है। ध्यान दें कि Project एक इंटरफ़ेस है लेकिन मैं DefaultProject प्रकार का एक विशिष्ट उदाहरण मजाक कर रहा हूं।

एक भी विधि को छोटा करने के लिए metaprogramming विधि इस उत्तर से है: https://stackoverflow.com/a/23818476/1509221

उत्तर

1

ग्रूवी में एक विधि का उपयोग कर metaClass टूट गया है एक अंतरफलक में परिभाषित की जगह। इस मामले में, exec विधि Project कक्षा में परिभाषित की गई है, जो एक इंटरफ़ेस है। GROOVY-3493 से (सूचना 2009 में मूल रूप से):

"Cannot override methods via metaclass that are part of an interface implementation" 

कारगर युक्तियाँ

invokeMethod अवरोध सभी तरीकों और काम कर सकते हैं। यह अधिक है लेकिन यह काम करता है। जब विधि का नाम exec से मेल खाता है, तो यह कॉल को mySpecialInstance ऑब्जेक्ट में बदल देता है। अन्यथा यह प्रतिनिधि, अर्थात् मौजूदा तरीकों से गुजरता है। इस पर इनपुट के लिए invokeMethod delegation और Logging All Methods पर धन्यवाद।

// This intercepts all methods, stubbing out exec and passing through all other invokes 
this.project.metaClass.invokeMethod = { String name, args -> 
    if (name == 'exec') { 
     // Call special instance to track verifications 
     mySpecialInstance.exec((Closure) args.first()) 
    } else { 
     // This calls the delegate without causing infinite recursion 
     MetaMethod metaMethod = delegate.class.metaClass.getMetaMethod(name, args) 
     return metaMethod?.invoke(delegate, args) 
    } 
} 

यह सिवाय आप के बारे में "तर्कों की गलत संख्या" या "अशक्त वस्तु पर विधि xxxxx आह्वान नहीं कर सकते" अपवाद देख सकते हैं कि अच्छी तरह से काम करता है। समस्या यह है कि उपरोक्त कोड विधि तर्कों के समन्वय को संभाल नहीं करता है। project.files(Object... paths) के लिए, invokeMethod के लिए तर्क [['path1', 'path2']] के रूप में होना चाहिए। लेकिन, कुछ मामलों में files(null) या files() पर कॉल किया गया है, इसलिए invokeMethod के लिए तर्क [null] और [] क्रमशः [[]] की अपेक्षा करते हैं। उपरोक्त त्रुटियों का निर्माण।

निम्न कोड केवल files विधि के लिए इस को हल करती है, लेकिन है कि मेरी इकाई परीक्षण के लिए पर्याप्त था। मैं अभी भी एक विधि को बदलने या आदर्श रूप से एक विधि को बदलने का एक बेहतर तरीका खोजना चाहता हूं।

// As above but handle coercing of the files parameter types 
this.project.metaClass.invokeMethod = { String name, args -> 
    if (name == 'exec') { 
     // Call special instance to track verifications 
     mySpecialInstance.exec((Closure) args.first()) 
    } else { 
     // This calls the delegate without causing infinite recursion 
     // https://stackoverflow.com/a/10126006/1509221 
     MetaMethod metaMethod = delegate.class.metaClass.getMetaMethod(name, args) 
     logInvokeMethod(name, args, metaMethod) 

     // Special case 'files' method which can throw exceptions 
     if (name == 'files') { 
      // Coerce the arguments to match the signature of Project.files(Object... paths) 
      // TODO: is there a way to do this automatically, e.g. coerceArgumentsToClasses? 
      assert 0 == args.size() || 1 == args.size() 

      if (args.size() == 0 || // files() 
       args.first() == null) { // files(null) 
       return metaMethod?.invoke(delegate, [[] as Object[]] as Object[]) 
      } else { 
       // files(ArrayList) possibly, so cast ArrayList to Object[] 
       return metaMethod?.invoke(delegate, [(Object[]) args.first()] as Object[]) 
      } 
     } else { 
      // Normal pass through 
      return metaMethod?.invoke(delegate, args) 
     } 
    } 
} 
+0

मुझे 100% यकीन नहीं है कि यह काम करेगा, लेकिन आप मेरे ब्लॉग पर ओवरराइडिंग विधियों के बारे में एक और पोस्ट में तकनीक का उपयोग करने में सक्षम हो सकते हैं लेकिन अभी भी मूल कार्यान्वयन का उपयोग कर सकते हैं: http://naleid.com/ब्लॉग/200 9/06/01/ग्रोवी-मेटाक्लास-ओवरराइडिंग-ए-विधि-जबकि-पुराने-कार्यान्वयन-पुराने-कार्यान्वयन यह इंटरफ़ेस समस्या द्वारा अवरुद्ध हो सकता है जिसे आपने पहचाना है ... यदि ऐसा है, तो समाधान जो आपने मेरे लिए एक अच्छा दिखता है। –

+0

धन्यवाद टेड, इंटरफ़ेस को काम करने से रोकने के साथ यह वही समस्या है। मुझे कॉरर्सिंग प्रकारों पर आपके विचारों में दिलचस्पी है। सरल कोड 'project.files (ऑब्जेक्ट ... पथ) 'के लिए' फ़ाइलें (शून्य) 'और' फ़ाइलें()' के लिए विफल रहता है। क्या एक सामान्य तरीके से एक varargs पैरामीटर के लिए प्रकारों को मजबूत करने का कोई बेहतर तरीका है? सुधार के साथ मेरी पोस्ट को संपादित करने के लिए स्वतंत्र महसूस करें। – brunobowden