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

时间:2015-06-30 03:24:02

标签: 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是一个界面,但我正在模拟DefaultProject类型的特定实例。

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

1 个答案:

答案 0 :(得分:1)

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

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

解决方法

invokeMethod拦截所有方法并且可以工作。这有点矫枉过正,但确实有效。当方法名称与exec匹配时,它会将调用转移到mySpecialInstance对象。否则它将传递给委托,即现有方法。感谢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的args分别为[null][],因为它预期{{1} }}。产生上述错误。

以下代码仅针对[[]]方法解决了这个问题,但这对我的单元测试来说已经足够了。我仍然希望找到一种更好的强制类型或更换单一方法的方法。

files