groovy - 使用 metaClass 模拟 Gradle project.exec {...}

标签 groovy gradle

作为测试 Gradle 插件的一部分,我想删除一个 groovy 方法:project.exec {...} .这是为了确认它正在进行正确的命令行调用。我正在尝试使用元编程:

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

奇怪的是,如果我重命名这两个 exec othername 的方法,然后它可以正常工作:
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是一个接口(interface),但我在模拟 DefaultProject 类型的特定实例.

stub 单个方法的元编程方法来自这个答案:https://stackoverflow.com/a/23818476/1509221

最佳答案

在 Groovy 中,使用 metaClass 替换接口(interface)中定义的方法被破坏了。在这种情况下,exec方法在 Project 中定义类,它是一个接口(interface)。来自 GROOVY-3493 (最初于 2009 年报道):

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

解决方法
invokeMethod拦截所有方法并且可以工作。这是矫枉过正,但它确实有效。当方法名匹配 exec ,它将调用转移到 mySpecialInstance目的。否则,它会传递给委托(delegate),即现有方法。感谢 invokeMethod delegationLogging 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)
        }
    }
}

关于groovy - 使用 metaClass 模拟 Gradle project.exec {...},我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31129003/

相关文章:

arrays - 常规数组。 [[],[]].toArray() 和 [[],[]]*.toArray() 之间有什么区别

groovy - 如何使用groovy解析xml

Gradle : Generating sources from WSDL & XSD and adding it to main classpath for compilation

android - 找不到 Gradle DSL 方法 'compile()'(firebase 冲突)

xml - 使用 groovy 遍历每个 xml 节点,打印每个节点

android - 如何在 Gradle 构建脚本(Android)中定义和使用常量?

android - 如何通过导入正确的库来避免 DEX 64K LIMIT

android - Gradle 禁用依赖项目的构建

android - flutter run无法确定任务 ':app:compileDebugJavaWithJavac'的依赖项

scala - Scala 和 Groovy 之间的主要区别是什么?