24 | * Redirects all {@link Script} interface methods to the wrapped Script, calling them 25 | * indirectly to prevent their names from causing problems with Java classloaders. 26 | *
27 | * 28 | * @see GROOVY-7670 29 | * 30 | * @author awitt 31 | * @since 2.1.1 32 | */ 33 | public class InvalidlyNamedScriptWrapper extends Script { 34 | 35 | protected Script script 36 | 37 | public InvalidlyNamedScriptWrapper(Script _script) { 38 | script = _script 39 | } 40 | 41 | @Override 42 | public Binding getBinding() { 43 | return script.getBinding() 44 | } 45 | 46 | @Override 47 | public void setBinding(Binding _binding) { 48 | script.setBinding( _binding ) 49 | } 50 | 51 | @Override 52 | public Object getProperty(String _property) { 53 | return script.getProperty( _property ) 54 | } 55 | 56 | @Override 57 | public void setProperty(String _property, Object _newValue) { 58 | script.setProperty( _property, _newValue ) 59 | } 60 | 61 | @Override 62 | public Object run() { 63 | return InvokerHelper.invokeMethod( script, "run", null ) 64 | } 65 | 66 | @Override 67 | public Object invokeMethod(String _name, Object _args) { 68 | return InvokerHelper.invokeMethod( script, _name, _args ) 69 | } 70 | } -------------------------------------------------------------------------------- /src/main/groovy/com/homeaway/devtools/jenkins/testing/PipelineVariableImpersonator.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2018 Expedia Group. 3 | All rights reserved. http://www.homeaway.com 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package com.homeaway.devtools.jenkins.testing 19 | 20 | import org.slf4j.Logger 21 | import org.slf4j.LoggerFactory 22 | 23 | /** 24 | * Takes the place of a {@link GlobalVariable} in a {@link JenkinsPipelineSpecification} context. 25 | * Intercepts every method call and forwards it to a mock for the current test-run. 26 | * Mocks are named with the patternGlobalVariableName.methodName
- for example, in the following pipeline script under test:
27 | *
28 | SomeVariable.someFunction("foo")
29 | *
30 | * The someFunction call could be verified by
31 | *
32 | then:
33 | 1 * getPipelineMock("SomeVariable.someFunction")("foo")
34 | *
35 | *
36 | * @author awitt
37 | *
38 | */
39 | public class PipelineVariableImpersonator {
40 |
41 | private static final Logger LOG = LoggerFactory.getLogger(PipelineVariableImpersonator.class)
42 |
43 | /**
44 | * The "Global Variable" property name to automatically generate mocks for.
45 | */
46 | protected String for_property
47 |
48 | /**
49 | * The test suite that in which to automatically generate mocks.
50 | */
51 | protected JenkinsPipelineSpecification for_spec
52 |
53 | /**
54 | * Create a stand-in for _property in the Spock _spec
55 | * @param _property The "Global Variable" property name to automatically generate mocks for.
56 | * @param _spec The test suite that in which to automatically generate mocks.
57 | */
58 | public PipelineVariableImpersonator(String _property, JenkinsPipelineSpecification _spec) {
59 | for_property = _property
60 | for_spec = _spec
61 | }
62 |
63 | /**
64 | * Intercept a missing method invocation on this "Global Variable" and try calling a mock with that name, instead.
65 | *
66 | * @param _name The name of the method that didn't exist
67 | * @param _args The arguments to that method
68 | *
69 | * @return The results of invoking {@link #for_spec}.getPipelineMock( "{@link #for_property}._name" )( _args )
70 | */
71 | def methodMissing(String _name, _args) {
72 |
73 | def prepared_args = _args
74 |
75 | if( ! ( prepared_args instanceof Object[] ) ) {
76 | // this method took one argument. Later on, unwrapping it will be WRONG, so, just pre-wrap it.
77 | prepared_args = [_args].toArray()
78 | }
79 |
80 | String key = "${for_property}.${_name}"
81 |
82 | Closure mock = for_spec.explicitlyMockPipelineStep( key, "(implicit-runtime) getPipelineMock(\"${key}\")" )
83 |
84 | JenkinsPipelineSpecification.LOG_CALL_INTERCEPT(
85 | "debug",
86 | "method call on pipeline variable [${for_property}]",
87 | this,
88 | "methodMissing",
89 | _name,
90 | _args,
91 | mock,
92 | "call",
93 | prepared_args,
94 | true )
95 |
96 | // use the actual closure Mock
97 | Object result = mock( *prepared_args )
98 |
99 | if( _args != null && _args.length >= 1 && _args[_args.length-1] instanceof Closure ) {
100 | // there was at least one argument, and the last argument was a Closure
101 | // this almost certainly means that the user's trying to pass the closure to the function as a "body"
102 | // they probably want it to execute right now.
103 | _args[_args.length-1]()
104 | }
105 |
106 | return result
107 | }
108 |
109 | def propertyMissing(String _name) {
110 | return for_spec.getPipelineMock( "${for_property}.getProperty" )( _name )
111 | }
112 |
113 | public String toString() {
114 | return "Mock Generator for [${for_property}]"
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/main/java/com/homeaway/devtools/jenkins/testing/APipelineExtensionDetector.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2018 Expedia Group.
3 | All rights reserved. http://www.homeaway.com
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package com.homeaway.devtools.jenkins.testing;
19 |
20 | import java.lang.annotation.Annotation;
21 | import java.util.Arrays;
22 | import java.util.HashMap;
23 | import java.util.HashSet;
24 | import java.util.Map;
25 | import java.util.Map.Entry;
26 | import java.util.Optional;
27 | import java.util.Set;
28 |
29 | import org.jenkinsci.Symbol;
30 | import org.jenkinsci.plugins.workflow.cps.GlobalVariable;
31 | import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
32 | import org.slf4j.Logger;
33 | import org.slf4j.LoggerFactory;
34 |
35 | import hudson.Extension;
36 |
37 | /**
38 | * Utility class for detecting extension points to Jenkins' Pipeline API.
39 | *
40 | * @author awitt
41 | *
42 | */
43 | public abstract class APipelineExtensionDetector {
44 |
45 | private static final Logger LOG = LoggerFactory.getLogger( APipelineExtensionDetector.class );
46 |
47 | /**
48 | * Find classes of the provided _supertype in the provided _package tree.
49 | *
50 | * @param _supertype The type of class to look for.
51 | * @param _package The package tree to look in
52 | *
53 | * @return classes of the provided _supertype in the provided _package tree.
54 | */
55 | public abstract Setparallel(...)
pipeline step is special-cased and
22 | * that any closures passed to it as arguments are executed.
23 | *
24 | * @author awitt
25 | *
26 | */
27 | public class ParallelClosureExecutionSpec extends JenkinsPipelineSpecification {
28 |
29 | def "parallel() closures are executed when there is only one closure"() {
30 | when:
31 | parallel(
32 | "stream1": {
33 | echo( "stream1" )
34 | }
35 | )
36 | then:
37 | 1 * getPipelineMock("echo")("stream1")
38 | }
39 |
40 | def "parallel() closures are executed when there are multiple closures"() {
41 | when:
42 | parallel(
43 | "stream1": {
44 | echo( "stream1" )
45 | },
46 | "stream2": {
47 | echo( "stream2" )
48 | }
49 | )
50 | then:
51 | 1 * getPipelineMock("echo")("stream1")
52 | 1 * getPipelineMock("echo")("stream2")
53 | }
54 |
55 | def "parallel() does not error when no closures are provided"() {
56 | when:
57 | parallel()
58 | then:
59 | 0 * getPipelineMock("echo")(_)
60 | }
61 |
62 | def "parallel() handles non-closure arguments"() {
63 | when:
64 | parallel(
65 | "failFast": true,
66 | "stream 1" : {
67 | echo( "hello 1" )
68 | },
69 | "stream 2" : {
70 | echo( "hello 2" )
71 | }
72 | )
73 | then:
74 | 1 * getPipelineMock("echo")("hello 1")
75 | 1 * getPipelineMock("echo")("hello 2")
76 | }
77 |
78 | def "parallel() mock is called even when its closures are subsequently executed"() {
79 | when:
80 | parallel(
81 | "stream1": {
82 | echo( "stream1" )
83 | },
84 | "stream2": {
85 | echo( "stream2" )
86 | }
87 | )
88 | then:
89 | 1 * getPipelineMock("parallel")(_)
90 | }
91 |
92 | def "parallel() arguments can be captured"() {
93 | when:
94 | parallel(
95 | "stream1": {
96 | echo( "stream1" )
97 | },
98 | "stream2": {
99 | echo( "stream2" )
100 | }
101 | )
102 | then:
103 | 1 * getPipelineMock("parallel")(_ as Map) >> { _parallel_args ->
104 | Map parallel_map = _parallel_args[0]
105 |
106 | assert parallel_map instanceof Map
107 | assert parallel_map.size() == 2
108 | assert parallel_map.containsKey( "stream1" )
109 | assert parallel_map.containsKey( "stream2" )
110 | }
111 | }
112 | }
--------------------------------------------------------------------------------
/src/test/groovy/com/homeaway/devtools/jenkins/testing/TrailngClosureExecutionSpec.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2018 Expedia Group.
3 | All rights reserved. http://www.homeaway.com
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package com.homeaway.devtools.jenkins.testing;
19 |
20 | /**
21 | * Verifies that "trailng" closures are executed when passed to mocks.
22 | * It's overwhelmingly common for these to be executed that way in Jenkins pipelines,
23 | * so much so that it's reasonable for the framework to stub this behavior for all mocks.
24 | *
25 | * @author awitt
26 | *
27 | */
28 | public class TrailingclosureExecutionSpec extends JenkinsPipelineSpecification {
29 |
30 | def setup() {
31 | explicitlyMockPipelineVariable("someSymbol")
32 | }
33 |
34 | def "STEP: trailing closure is executed when passed implicitly & is the only argument"() {
35 | when:
36 | node() {
37 | echo( "payload" )
38 | }
39 | then:
40 | 1 * getPipelineMock("echo")("payload")
41 | }
42 |
43 | def "STEP: trailing closure is executed when passed implicitly & is not the only argument"() {
44 | when:
45 | node("label") {
46 | echo( "payload" )
47 | }
48 | then:
49 | 1 * getPipelineMock("echo")("payload")
50 | }
51 |
52 | def "STEP: trailing closure is executed when passed explicitly & is the only argument"() {
53 | when:
54 | node({ echo("payload")})
55 | then:
56 | 1 * getPipelineMock("echo")("payload")
57 | }
58 |
59 | def "STEP: trailing closure is executed when passed explicitly & is not the only argument"() {
60 | when:
61 | node("label", { echo("payload")})
62 | then:
63 | 1 * getPipelineMock("echo")("payload")
64 | }
65 |
66 | def "SYMBOL: trailing closure is executed when passed implicitly & is the only argument"() {
67 | when:
68 | someSymbol() {
69 | echo( "payload" )
70 | }
71 | then:
72 | 1 * getPipelineMock("echo")("payload")
73 | }
74 |
75 | def "SYMBOL: trailing closure is executed when passed implicitly & is not the only argument"() {
76 | when:
77 | someSymbol("label") {
78 | echo( "payload" )
79 | }
80 | then:
81 | 1 * getPipelineMock("echo")("payload")
82 | }
83 |
84 | def "SYMBOL: trailing closure is executed when passed explicitly & is the only argument"() {
85 | when:
86 | someSymbol({ echo("payload")})
87 | then:
88 | 1 * getPipelineMock("echo")("payload")
89 | }
90 |
91 | def "SYMBOL: trailing closure is executed when passed explicitly & is not the only argument"() {
92 | when:
93 | someSymbol("label", { echo("payload")})
94 | then:
95 | 1 * getPipelineMock("echo")("payload")
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/test/groovy/com/homeaway/devtools/jenkins/testing/functions/ClassToTest.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2018 Expedia Group.
3 | All rights reserved. http://www.homeaway.com
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package com.homeaway.devtools.jenkins.testing.functions
19 |
20 | /**
21 | * A regular Groovy class that calls pipeline steps.
22 | * @author awitt
23 | *
24 | */
25 | class ClassToTest {
26 | public Map helloNode(String _label, Closure _body) {
27 | return node( _label ) {
28 | echo( "Hello from a [${_label}] node!" )
29 | _body()
30 | echo( "Goodbye from a [${_label}] node!" )
31 | }
32 | }
33 |
34 | public methodCall() {
35 | echo( "called methodCall" )
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/test/groovy/com/homeaway/devtools/jenkins/testing/functions/ClassToTestSpec.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2018 Expedia Group.
3 | All rights reserved. http://www.homeaway.com
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package com.homeaway.devtools.jenkins.testing.functions
19 |
20 | import com.homeaway.devtools.jenkins.testing.JenkinsPipelineSpecification
21 |
22 | /**
23 | * Given a regular Groovy class that tries to call pipeline steps (here {@link ClassToTest}),
24 | * verify that those calls can be mocked.
25 | *
26 | * Further verify that calls that can't be dispatched to a mock are handled correctly.
27 | *
28 | * @author awitt
29 | *
30 | */
31 | public class ClassToTestSpec extends JenkinsPipelineSpecification {
32 | def "helloNode" () {
33 | given:
34 | ClassToTest myVar = new ClassToTest()
35 | when:
36 | myVar.helloNode( "nodeType" ) {
37 | echo( "inside node" )
38 | }
39 | then:
40 | 1 * getPipelineMock( "node" )("nodeType", _)
41 | 1 * getPipelineMock( "echo" )("Hello from a [nodeType] node!")
42 | 1 * getPipelineMock( "echo" )("Goodbye from a [nodeType] node!")
43 | 1 * getPipelineMock( "echo" )("inside node")
44 | }
45 |
46 | def "calling method with correct parameters"() {
47 | given:
48 | ClassToTest myVar = new ClassToTest()
49 | when:
50 | myVar.methodCall()
51 | then:
52 | 1 * getPipelineMock( "echo")( "called methodCall" )
53 | }
54 |
55 | def "calling method with incorrect parameters" () {
56 | given:
57 | ClassToTest myVar = new ClassToTest()
58 | when:
59 | myVar.methodCall( "incorrectParameter" )
60 | then:
61 | thrown MissingMethodException
62 | }
63 |
64 | def "calling nonexistent method"() {
65 | given:
66 | ClassToTest myVar = new ClassToTest()
67 | when:
68 | myVar.nonexistentMethod()
69 | then:
70 | thrown IllegalStateException
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/test/groovy/com/homeaway/devtools/jenkins/testing/functions/ClassWithMissingHandlers.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2018 Expedia Group.
3 | All rights reserved. http://www.homeaway.com
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package com.homeaway.devtools.jenkins.testing.functions
19 |
20 | /**
21 | * A regular Groovy class that also implements methodMissing
and propertyMissing
handlers.
22 | *
23 | * @author awitt
24 | *
25 | */
26 | class ClassWithMissingHandlers {
27 |
28 | public Map helloNode(String _label, Closure _body) {
29 | return node( _label ) {
30 | echo( "Hello from a [${_label}] node!" )
31 | _body()
32 | echo( "Goodbye from a [${_label}] node!" )
33 | }
34 | }
35 |
36 | def methodMissing(String _name, _args) {
37 |
38 | if( _name == "sh" ) {
39 | return "myCustomShReturnValue"
40 | }
41 |
42 | throw new MissingMethodException( _name, getClass(), _args )
43 | }
44 |
45 | def propertyMissing(String _name) {
46 |
47 | if( _name == "dynamicProperty" ) {
48 | return "myCustomDynamicPropertyValue"
49 | }
50 |
51 | throw new MissingPropertyException( _name, getClass() )
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/test/groovy/com/homeaway/devtools/jenkins/testing/functions/ClassWithMissingHandlersSpec.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2018 Expedia Group.
3 | All rights reserved. http://www.homeaway.com
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package com.homeaway.devtools.jenkins.testing.functions
19 |
20 | import com.homeaway.devtools.jenkins.testing.JenkinsPipelineSpecification
21 | import com.homeaway.devtools.jenkins.testing.PipelineVariableImpersonator
22 |
23 | /**
24 | * When a class-under-test defines its own methodMissing
or propertyMissing
25 | * methods, verify {@link JenkinsPipelineSpecification}'s best-effort attempt to delegate
26 | * to those handlers while still enabling mock pipeline steps and variables to be accessed
27 | *
28 | * @author awitt
29 | *
30 | */
31 | public class ClassWithMissingHandlersSpec extends JenkinsPipelineSpecification {
32 |
33 | ClassWithMissingHandlers clazz
34 |
35 | def setup() {
36 | clazz = new ClassWithMissingHandlers()
37 | }
38 |
39 | def "normal methods work normally" () {
40 | when:
41 | clazz.helloNode( "nodeType" ) {
42 | echo( "inside node" )
43 | }
44 | then:
45 | 1 * getPipelineMock( "node" )("nodeType", _)
46 | 1 * getPipelineMock( "echo" )("Hello from a [nodeType] node!")
47 | 1 * getPipelineMock( "echo" )("Goodbye from a [nodeType] node!")
48 | 1 * getPipelineMock( "echo" )("inside node")
49 | }
50 |
51 | def "truly missing methods raise exception" () {
52 | when:
53 | clazz.madeUpMethod( "foo" )
54 | then:
55 | thrown IllegalStateException
56 | }
57 |
58 | def "missing pipeline steps hit the mock" () {
59 | when:
60 | clazz.stage("someStage") {
61 | "cats"
62 | }
63 | then:
64 | 1 * getPipelineMock("stage")( "someStage", _ )
65 | }
66 |
67 | def "class' own methodMissing is preferred" () {
68 | given:
69 | def retval = clazz.sh( "foo" )
70 | expect:
71 | "myCustomShReturnValue" == retval
72 | }
73 |
74 | def "truly missing properties raise exception" () {
75 | when:
76 | clazz.madeUpProperty
77 | then:
78 | thrown IllegalStateException
79 | }
80 |
81 | def "missing pipeline vars hit the mock" () {
82 | setup:
83 | explicitlyMockPipelineVariable( "DefinedGlobalVariable" )
84 | def real_global_var = clazz.DefinedGlobalVariable
85 | expect:
86 | real_global_var != null
87 | real_global_var instanceof PipelineVariableImpersonator
88 | }
89 |
90 | def "class' own propertyMissing is preferred" () {
91 | given:
92 | def retval = clazz.dynamicProperty
93 | expect:
94 | "myCustomDynamicPropertyValue" == retval
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/src/test/groovy/com/homeaway/devtools/jenkins/testing/functions/ClassWithNoMethods.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2019 Expedia Group.
3 | All rights reserved. http://www.homeaway.com
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package com.homeaway.devtools.jenkins.testing.functions
19 |
20 | /**
21 | * A class with no methods.
22 | * @author awitt
23 | *
24 | */
25 | class ClassWithNoMethods {}
26 |
--------------------------------------------------------------------------------
/src/test/groovy/com/homeaway/devtools/jenkins/testing/functions/ClassWithNoMethodsSpec.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2019 Expedia Group.
3 | All rights reserved. http://www.homeaway.com
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package com.homeaway.devtools.jenkins.testing.functions
19 |
20 | import com.homeaway.devtools.jenkins.testing.JenkinsPipelineSpecification
21 |
22 | /**
23 | * Verify that classes without methods don't trip up the "failed to dispatch a call" logic
24 | *
25 | * @author awitt
26 | *
27 | */
28 | public class ClassWithNoMethodsSpec extends JenkinsPipelineSpecification {
29 |
30 | def "calling nonexistent method"() {
31 | given:
32 | ClassToTest myVar = new ClassToTest()
33 | when:
34 | myVar.nonexistentMethod()
35 | then:
36 | thrown IllegalStateException
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/test/groovy/com/homeaway/devtools/jenkins/testing/jobs/JenkinsfileSpec.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2018 Expedia Group.
3 | All rights reserved. http://www.homeaway.com
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package com.homeaway.devtools.jenkins.testing.jobs
19 |
20 | import com.homeaway.devtools.jenkins.testing.JenkinsPipelineSpecification
21 |
22 | /**
23 | * Given a classpath resource that is a whole pipeline script,
24 | * verify that {@link JenkinsPipelineSpecification#loadPipelineScriptForTest(java.lang.String)}
25 | * can successfully load that script and instrument its code to hit the mocks.
26 | *
27 | * @author awitt
28 | *
29 | */
30 | public class JenkinsfileSpec extends JenkinsPipelineSpecification {
31 | def "Jenkinsfile"() {
32 | setup:
33 | def Jenkinsfile = loadPipelineScriptForTest("com/homeaway/devtools/jenkins/testing/jobs/Jenkinsfile.groovy")
34 | getPipelineMock("node")(_ as String, _ as Closure ) >> { String _label, Closure _body ->
35 | _body()
36 | }
37 | when:
38 | Jenkinsfile.run()
39 | then:
40 | 1 * getPipelineMock("node")("legacy", _)
41 | 1 * getPipelineMock("echo")("hello world")
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/test/groovy/com/homeaway/devtools/jenkins/testing/properties/JenkinsfileWithExplicitlyDefinedPropertySpec.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2018 Expedia Group.
3 | All rights reserved. http://www.homeaway.com
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package com.homeaway.devtools.jenkins.testing.properties
19 |
20 | import com.homeaway.devtools.jenkins.testing.JenkinsPipelineSpecification
21 | import com.homeaway.devtools.jenkins.testing.PipelineVariableImpersonator
22 |
23 | /**
24 | * Given a whole pipeline script that attempts to access a variable that has been explicitly mocked with
25 | * {@link JenkinsPipelineSpecification#explicitlyMockPipelineVariable(java.lang.String)}, verify
26 | * that a mock for that variable is successfully created and connected.
27 | *
28 | * @author awitt
29 | *
30 | */
31 | public class JenkinsfileWithExplicitlyDefinedPropertySpec extends JenkinsPipelineSpecification {
32 |
33 | protected Script Jenkinsfile
34 |
35 | def setupSpec() {
36 | JenkinsPipelineSpecification.metaClass = null
37 | PipelineVariableImpersonator.metaClass = null
38 | }
39 |
40 | def setup() {
41 |
42 | // mock the variable "env"
43 | explicitlyMockPipelineVariable("env")
44 |
45 | // Load the Jenkinsfile
46 | Jenkinsfile = loadPipelineScriptForTest("com/homeaway/devtools/jenkins/testing/properties/JenkinsfileWithExplicitlyDefinedProperty.groovy")
47 | }
48 |
49 | def "variable.propertyAccess" () {
50 | when:
51 | Jenkinsfile.run()
52 | then:
53 | 1 * getPipelineMock("stage")("property access", _)
54 | 1 * getPipelineMock("env.getProperty")("someEnvVar") >> "EXPECTED ENVVAR VALUE"
55 | 1 * getPipelineMock("echo")("EXPECTED ENVVAR VALUE")
56 | }
57 |
58 | def "inline property accesses" () {
59 | when:
60 | String someEnvvar = env.someEnvvar
61 | String otherEnvvar = env.otherEnvvar
62 | then:
63 | 1 * getPipelineMock( "env.getProperty" )( "someEnvvar" )
64 | 1 * getPipelineMock( "env.getProperty" )( "otherEnvvar" ) >> "expected"
65 | expect:
66 | "expected" == otherEnvvar
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/test/groovy/com/homeaway/devtools/jenkins/testing/properties/JenkinsfileWithPropertySpec.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2018 Expedia Group.
3 | All rights reserved. http://www.homeaway.com
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package com.homeaway.devtools.jenkins.testing.properties
19 |
20 | import com.homeaway.devtools.jenkins.testing.JenkinsPipelineSpecification
21 | import com.homeaway.devtools.jenkins.testing.PipelineVariableImpersonator
22 |
23 | /**
24 | * Given a whole pipeline script that attempts to access a variable from an extension point
25 | * (likely a {@link org.jenkinsci.Symbol Symbol} or {@link org.jenkinsci.plugins.workflow.cps.GlobalVariable GlobalVariable}), verify that the correct mock object
26 | * is present and connected.
27 | *
28 | * @author awitt
29 | *
30 | */
31 | public class JenkinsfileWithPropertySpec extends JenkinsPipelineSpecification {
32 |
33 | protected Script Jenkinsfile
34 |
35 | def setupSpec() {
36 | JenkinsPipelineSpecification.metaClass = null
37 | PipelineVariableImpersonator.metaClass = null
38 | }
39 |
40 | def setup() {
41 | // Load the Jenkinsfile
42 | Jenkinsfile = loadPipelineScriptForTest("com/homeaway/devtools/jenkins/testing/properties/JenkinsfileWithProperty.groovy")
43 | }
44 |
45 | def "Jenkinsfile"() {
46 | when:
47 | Jenkinsfile.run()
48 | then:
49 | 1 * getPipelineMock("node")("legacy", _)
50 | 1 * getPipelineMock("echo")("the end")
51 | expect:
52 | null != mocks
53 |
54 | null != mocks.get("docker")
55 | null != getPipelineMock("docker")
56 | mocks.get("docker") instanceof PipelineVariableImpersonator
57 | }
58 |
59 | def "expected method invocation on global variable" () {
60 | when:
61 | Jenkinsfile.run()
62 | then:
63 | 1 * getPipelineMock("stage")("expected", _)
64 | 1 * getPipelineMock("docker.expectedInvocation")("expected")
65 | expect:
66 | null != mocks.get("docker.expectedInvocation")
67 | null != getPipelineMock("docker.expectedInvocation")
68 | mocks.get("docker.expectedInvocation") instanceof Closure
69 | "Mock for type 'Closure' named '(implicit-expected) getPipelineMock(\"docker.expectedInvocation\")'" == mocks.get("docker.expectedInvocation").toString()
70 | }
71 |
72 | def "unexpected method invocation on global variable" () {
73 | when:
74 | Jenkinsfile.run()
75 | then:
76 | 1 * getPipelineMock("stage")("unexpected", _)
77 | expect:
78 | null != mocks.get("docker.unexpectedInvocation")
79 | null != getPipelineMock("docker.unexpectedInvocation")
80 | mocks.get("docker.unexpectedInvocation") instanceof Closure
81 | "Mock for type 'Closure' named '(implicit-runtime) getPipelineMock(\"docker.unexpectedInvocation\")'" == mocks.get("docker.unexpectedInvocation").toString()
82 | }
83 |
84 | def "stubbed method invocation on global variable" () {
85 | when:
86 | Jenkinsfile.run()
87 | then:
88 | 1 * getPipelineMock("stage")("stubbed", _)
89 | 1 * getPipelineMock("docker.stubbedInvocation")("original input from script") >> "stubbed"
90 | 1 * getPipelineMock("docker.stubbedInvocation")("stubbed")
91 | expect:
92 | null != mocks.get("docker.stubbedInvocation")
93 | null != getPipelineMock("docker.stubbedInvocation")
94 | mocks.get("docker.stubbedInvocation") instanceof Closure
95 | "Mock for type 'Closure' named '(implicit-expected) getPipelineMock(\"docker.stubbedInvocation\")'" == mocks.get("docker.stubbedInvocation").toString()
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/test/groovy/com/homeaway/devtools/jenkins/testing/properties/JenkinsfileWithUndefinedPropertySpec.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2018 Expedia Group.
3 | All rights reserved. http://www.homeaway.com
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package com.homeaway.devtools.jenkins.testing.properties
19 |
20 | import com.homeaway.devtools.jenkins.testing.JenkinsPipelineSpecification
21 | import com.homeaway.devtools.jenkins.testing.PipelineVariableImpersonator
22 |
23 | /**
24 | * Given a whole pipeline script that attempts to access a variable that is truly not defined,
25 | * verify that the appropriate exception is thrown.
26 | *
27 | * @author awitt
28 | *
29 | */
30 | public class JenkinsfileWithUndefinedPropertySpec extends JenkinsPipelineSpecification {
31 |
32 | protected Script Jenkinsfile
33 |
34 | def setupSpec() {
35 | JenkinsPipelineSpecification.metaClass = null
36 | PipelineVariableImpersonator.metaClass = null
37 | }
38 |
39 | def setup() {
40 | // Load the Jenkinsfile
41 | Jenkinsfile = loadPipelineScriptForTest("com/homeaway/devtools/jenkins/testing/properties/JenkinsfileWithUndefinedProperty.groovy")
42 | }
43 |
44 | def "fails"() {
45 | when:
46 | Jenkinsfile.run()
47 | then:
48 | thrown IllegalStateException
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/test/groovy/com/homeaway/devtools/jenkins/testing/scripts/InvalidClassNameSpec.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2020 Expedia Group.
3 | All rights reserved. http://www.homeaway.com
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package com.homeaway.devtools.jenkins.testing.scripts;
19 |
20 | import com.homeaway.devtools.jenkins.testing.InvalidlyNamedScriptWrapper
21 | import com.homeaway.devtools.jenkins.testing.JenkinsPipelineSpecification
22 |
23 | /**
24 | * Groovy script contents can be in files with any name.
25 | * Unlike compiled classes, the names don't have to be valid JVM class names.
26 | *
27 | * Ensure that we can still test such scripts.
28 | *
29 | * @author awitt
30 | * @since 2.1.1
31 | */
32 | public class InvalidClassNameSpec extends JenkinsPipelineSpecification {
33 |
34 | def "scripts whose names create invalid JVM class names are wrapped"() {
35 | setup:
36 | def a_script = loadPipelineScriptForTest( "com/homeaway/devtools/jenkins/testing/scripts/some-script.groovy" )
37 | expect:
38 | InvalidlyNamedScriptWrapper.class == a_script.getClass()
39 | }
40 |
41 | def "scripts whose names create valid JVM class names are not wrapped"() {
42 | setup:
43 | def a_script = loadPipelineScriptForTest( "com/homeaway/devtools/jenkins/testing/scripts/some_script.groovy" )
44 | expect:
45 | // Groovy generates subclasses of Script for each Script
46 | Script.class.isAssignableFrom( a_script.getClass() )
47 | }
48 |
49 | def "can run scripts whose names create invalid JVM class names"() {
50 | setup:
51 | def a_script = loadPipelineScriptForTest( "com/homeaway/devtools/jenkins/testing/scripts/some-script.groovy" )
52 | when:
53 | a_script.run()
54 | then:
55 | 1 * getPipelineMock( "echo" )( "hello" )
56 | 1 * getPipelineMock( "echo" )( "helped" )
57 | }
58 |
59 | def "can run methods in scripts whose names create invalid JVM class names"() {
60 | setup:
61 | def a_script = loadPipelineScriptForTest( "com/homeaway/devtools/jenkins/testing/scripts/some-script.groovy" )
62 | when:
63 | a_script.helper_method()
64 | then:
65 | 1 * getPipelineMock( "echo" )( "helped" )
66 | }
67 |
68 | def "can get global variables in scripts whose names create invalid JVM class names"() {
69 | setup:
70 | def a_script = loadPipelineScriptForTest( "com/homeaway/devtools/jenkins/testing/scripts/some-script.groovy" )
71 | expect:
72 | "GLOBAL" == a_script.global_variable
73 | }
74 |
75 | def "can set script variables in scripts whose names create invalid JVM class names"() {
76 | setup:
77 | def a_script = loadPipelineScriptForTest( "com/homeaway/devtools/jenkins/testing/scripts/some-script.groovy" )
78 | a_script.getBinding().setVariable("new_property", "expected")
79 | expect:
80 | "expected" == a_script.getBinding().getVariable("new_property")
81 | }
82 |
83 | }
84 |
85 |
--------------------------------------------------------------------------------
/src/test/java/com/homeaway/devtools/jenkins/testing/DescriptorTimeJenkinsAccessingStep.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2020 Expedia Group.
3 | All rights reserved. http://www.homeaway.com
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package com.homeaway.devtools.jenkins.testing;
19 |
20 | import java.util.HashSet;
21 | import java.util.List;
22 | import java.util.Set;
23 |
24 | import org.jenkinsci.plugins.workflow.steps.Step;
25 | import org.jenkinsci.plugins.workflow.steps.StepContext;
26 | import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
27 | import org.jenkinsci.plugins.workflow.steps.StepExecution;
28 |
29 | import hudson.Extension;
30 | import hudson.model.Item;
31 | import jenkins.model.Jenkins;
32 |
33 | /**
34 | * A step that tries to call {@link Jenkins#getInstanceOrNull()} when its descriptor is instantiated.
35 | * 36 | * This means that a Jenkins must exist in order for code to even figure out the name of this step. 37 | * Any Jenkins-Spock specification should fail with this class on the classpath, unless Jenkins-Spock 38 | * can successfully handle the situation. 39 | *
40 | *
41 | * This step further tries to interact with a result of a method call on the Jenkins object,
42 | * if the system property jenkins.test-access.descriptor
is equal to the String true
.
43 | * This allows test cases in jenkins-spock to demonstrate techniques for stubbing interactions with
44 | * this static Jenkins, while allowing test-cases that aren't related to the static Jenkins to continue
45 | * without issue.
46 | *