| /* |
| * 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) |
| } |
| } |