blob: 7d7baa99198ddd78df7170e3b32768fe6872c909 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package typing
import groovy.$Temp
import groovy.test.GroovyAssert
import groovy.transform.TypeChecked
import groovy.xml.MarkupBuilder
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.MultipleCompilationErrorsException
import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
import static asciidoctor.Utils.stripAsciidocMarkup
class TypeCheckingExtensionSpecTest extends GroovyTestCase {
void testIntro() {
def out = new PrintWriter(new ByteArrayOutputStream())
// tag::intro_stc_extensions[]
def builder = new MarkupBuilder(out)
builder.html {
head {
// ...
}
body {
p 'Hello, world!'
}
}
// end::intro_stc_extensions[]
}
void testRobotExample() {
def err = shouldFail(MultipleCompilationErrorsException, '''import groovy.transform.TypeChecked
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
import typing.Robot
def script = """
// tag::example_robot_script[]
robot.move 100
// end::example_robot_script[]
"""
// tag::example_robot_setup[]
def config = new CompilerConfiguration()
config.addCompilationCustomizers(
new ASTTransformationCustomizer(TypeChecked) // <1>
)
def shell = new GroovyShell(config) // <2>
def robot = new Robot()
shell.setVariable('robot', robot)
shell.evaluate(script) // <3>
// end::example_robot_setup[]
''')
assert err.contains(stripAsciidocMarkup('''
// tag::example_robot_expected_err[]
[Static type checking] - The variable [robot] is undeclared.
// end::example_robot_expected_err[]
'''))
}
void testRobotExampleFixed() {
assertScript '''import groovy.transform.TypeChecked
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
import typing.Robot
def script = """
robot.move 100
"""
def config = new CompilerConfiguration()
// tag::example_robot_fixed_conf[]
config.addCompilationCustomizers(
new ASTTransformationCustomizer(
TypeChecked,
extensions:['robotextension.groovy'])
)
// end::example_robot_fixed_conf[]
def shell = new GroovyShell(config)
def robot = new Robot()
shell.setVariable('robot', robot)
shell.evaluate(script)
'''
}
void testSetup() {
assertScriptWithExtension('setup.groovy', '''
1+1
''')
}
void testFinish() {
assertScriptWithExtension('finish.groovy', '''
1+1
''')
}
void testUnresolvedVariable() {
assertScriptWithExtension('unresolvedvariable.groovy', '''
assert people.size() == 2
''') {
it.setVariable('people', ['John','Meg'])
}
}
void testUnresolvedProperty() {
use (SpecSupport) {
assertScriptWithExtension('unresolvedproperty.groovy', '''
assert 'string'.longueur == 6
''')
}
}
void testUnresolvedAttribute() {
try {
assertScriptWithExtension('unresolvedattribute.groovy', '''
assert 'string'.@longueur == 6
''')
assert false
} catch (MissingFieldException mfe) {
// ok
}
}
void testBeforeMethodCall() {
try {
assertScriptWithExtension('beforemethodcall.groovy', '''
'string'.toUpperCase()
''')
assert false
} catch (MultipleCompilationErrorsException err) {
assert err.message.contains('[Static type checking] - Not allowed')
}
}
void testAfterMethodCall() {
try {
assertScriptWithExtension('aftermethodcall.groovy', '''
'string'.toUpperCase()
''')
assert false
} catch (MultipleCompilationErrorsException err) {
assert err.message.contains('[Static type checking] - Not allowed')
}
}
void testOnMethodSelection() {
try {
assertScriptWithExtension('onmethodselection.groovy', '''
'string'.toUpperCase()
'string 2'.toLowerCase()
'string 3'.length()
''')
assert false
} catch (MultipleCompilationErrorsException err) {
assert err.message.contains('[Static type checking] - You can use only 2 calls on String in your source code')
}
}
void testMethodNotFound() {
use (SpecSupport) {
assertScriptWithExtension('methodnotfound.groovy', '''
assert 'string'.longueur() == 6
''')
}
}
void testBeforeVisitMethod() {
use (SpecSupport) {
assertScriptWithExtension('beforevisitmethod.groovy', '''
void skipIt() {
'blah'.doesNotExist()
}
skipIt()
''')
}
}
void testAfterVisitMethod() {
try {
assertScriptWithExtension('aftervisitmethod.groovy', '''
void foo() {
'string'.toUpperCase()
'string 2'.toLowerCase()
'string 3'.length()
}
foo()
''')
assert false
} catch (MultipleCompilationErrorsException err) {
assert err.message.contains('[Static type checking] - Method foo contains more than 2 method calls')
}
}
void testBeforeVisitClass() {
try {
assertScriptWithExtension('beforevisitclass.groovy', '''
class someclass {
}
''')
assert false
} catch (MultipleCompilationErrorsException err) {
assert err.message.contains("[Static type checking] - Class 'someclass' doesn't start with an uppercase letter")
}
}
void testAfterVisitClass() {
try {
assertScriptWithExtension('aftervisitclass.groovy', '''
class someclass {
}
''')
assert false
} catch (MultipleCompilationErrorsException err) {
assert err.message.contains("[Static type checking] - Class 'someclass' doesn't start with an uppercase letter")
}
}
void testIncompatibleAssignment() {
use (SpecSupport) {
assertScriptWithExtension('incompatibleassignment.groovy', '''import groovy.transform.TypeChecked
import groovy.transform.TypeCheckingMode
@TypeChecked(TypeCheckingMode.SKIP)
class Point {
int x, y = 1
void setProperty(String name, value) {
def v = value instanceof Closure ? value() : value
this.@"$name" *= v
}
}
def p = new Point(x: 3, y: 4)
p.x = { 2 }
assert p.x == 6
''')
}
}
void testAmbiguousMethods() {
def err = shouldFail {
assertScriptWithExtension('ambiguousmethods.groovy', '''
int foo(Integer x) { 1 }
int foo(String s) { 2 }
int foo(Date d) { 3 }
assert foo(null) == 2
''')
}
assert err.contains(/Cannot resolve which method to invoke for [null] due to overlapping prototypes/)
}
void testSupportMethods() {
assertScriptWithExtension('selfcheck.groovy','''
class Foo {}
1+1
''')
}
void testNewMethod() {
assertScriptWithExtension('newmethod.groovy','''
class Foo {
def methodMissing(String name, args) { this }
}
def f = new Foo()
f.foo().bar()
''')
}
void testScopingMethods() {
assertScriptWithExtension('scoping.groovy','''
1+1
''')
assertScriptWithExtension('scoping_alt.groovy','''
1+1
''')
}
void testPrecompiledExtensions() {
assertScript '''import groovy.transform.TypeChecked
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
import typing.Robot
def script = """
robot.move 100
"""
def config = new CompilerConfiguration()
// tag::setup_precompiled[]
config.addCompilationCustomizers(
new ASTTransformationCustomizer(
TypeChecked,
extensions:['typing.PrecompiledExtension'])
)
// end::setup_precompiled[]
def shell = new GroovyShell(config)
def robot = new Robot()
shell.setVariable('robot', robot)
shell.evaluate(script)
'''
assertScript '''import groovy.transform.TypeChecked
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
import typing.Robot
def script = """
robot.move 100
"""
def config = new CompilerConfiguration()
config.addCompilationCustomizers(
new ASTTransformationCustomizer(
TypeChecked,
extensions:['typing.PrecompiledJavaExtension'])
)
def shell = new GroovyShell(config)
def robot = new Robot()
shell.setVariable('robot', robot)
shell.evaluate(script)
'''
}
void testRobotExamplePassWithCompileStatic() {
assertScript '''import groovy.transform.CompileStatic
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
import typing.Robot
def script = """
robot.move 100
"""
// tag::example_robot_setup_compilestatic[]
def config = new CompilerConfiguration()
config.addCompilationCustomizers(
new ASTTransformationCustomizer(
CompileStatic, // <1>
extensions:['robotextension.groovy']) // <2>
)
def shell = new GroovyShell(config)
def robot = new Robot()
shell.setVariable('robot', robot)
shell.evaluate(script)
// end::example_robot_setup_compilestatic[]
'''
}
void testRobotExampleDelegatingScript() {
assertScript '''import groovy.transform.CompileStatic
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
import typing.Robot
def script = """
// tag::example_robot_script_direct[]
move 100
// end::example_robot_script_direct[]
"""
// tag::example_robot_setup_dynamic[]
def config = new CompilerConfiguration()
config.scriptBaseClass = 'groovy.util.DelegatingScript' // <1>
def shell = new GroovyShell(config)
def runner = shell.parse(script) // <2>
runner.setDelegate(new Robot()) // <3>
runner.run() // <4>
// end::example_robot_setup_dynamic[]
'''
}
void testRobotExampleFailsWithCompileStatic() {
def err = GroovyAssert.shouldFail '''import groovy.transform.CompileStatic
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
import typing.Robot
def script = """
move 100
"""
def config = new CompilerConfiguration()
config.scriptBaseClass = 'groovy.util.DelegatingScript'
// tag::example_robot_setup_compilestatic2[]
config.addCompilationCustomizers(
new ASTTransformationCustomizer(
CompileStatic, // <1>
extensions:['robotextension2.groovy']) // <2>
)
// end::example_robot_setup_compilestatic2[]
def shell = new GroovyShell(config)
def runner = shell.parse(script)
runner.setDelegate(new Robot())
runner.run()
'''
err = "${err.class.name}: ${err.message}"
assert err.contains(stripAsciidocMarkup('''
// tag::robot_runtime_error_cs[]
java.lang.NoSuchMethodError: java.lang.Object.move()Ltyping/Robot;
// end::robot_runtime_error_cs[]
'''))
}
void testRobotExamplePassesWithCompileStatic() {
assertScript '''import groovy.transform.CompileStatic
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
import typing.Robot
def script = """
move 100
"""
def config = new CompilerConfiguration()
config.scriptBaseClass = 'groovy.util.DelegatingScript'
config.addCompilationCustomizers(
new ASTTransformationCustomizer(
CompileStatic,
extensions:['robotextension3.groovy'])
)
def shell = new GroovyShell(config)
def runner = shell.parse(script)
runner.setDelegate(new Robot())
runner.run()
'''
}
private static class SpecSupport {
static int getLongueur(String self) { self.length() }
static int longueur(String self) { self.length() }
static void doesNotExist(String self) {}
}
private def assertScriptWithExtension(String extensionName, String code, Closure<Void> configurator=null) {
def config = new CompilerConfiguration()
config.addCompilationCustomizers(
new ASTTransformationCustomizer(TypeChecked, extensions:[extensionName]))
def binding = new Binding()
def shell = new GroovyShell(binding,config)
if (configurator) {
configurator.call(binding)
}
shell.evaluate(code)
}
}