All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.gradle.api.provider.MapPropertyIntegrationTest.groovy Maven / Gradle / Ivy

/*
 * Copyright 2018 the original author or authors.
 *
 * Licensed 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 org.gradle.api.provider

import org.gradle.integtests.fixtures.AbstractIntegrationSpec
import spock.lang.Unroll

class MapPropertyIntegrationTest extends AbstractIntegrationSpec {
    def setup() {
        buildFile << '''
            abstract class AbstractVerificationTask extends DefaultTask {

                final MapProperty prop
                Map expected = [:]
                final Class keyType
                final Class valueType
                
                AbstractVerificationTask(Class keyType, Class valueType) {
                    this.keyType = keyType
                    this.valueType = valueType
                    prop = project.objects.mapProperty(keyType, valueType)
                }
                
                @TaskAction
                void validate() {
                    def actual = prop.getOrNull()
                    println "Actual: $actual"
                    println "Expected: $expected"
                    assert expected == actual
                    actual.each {
                        assert keyType.isInstance(it.key)
                        assert valueType.isInstance(it.value)
                    }
                }            
            }

            class StringVerificationTask extends AbstractVerificationTask {
                StringVerificationTask() { super(String, String) }
            }
            
            class IntegerVerificationTask extends AbstractVerificationTask {
                IntegerVerificationTask() { super(Integer, Integer) }
            }
            
            task verify(type: StringVerificationTask)
            task verifyInt(type: IntegerVerificationTask)
            '''
    }

    def "can finalize the value of a property using API"() {
        given:
        buildFile << '''
            int counter = 0
            def provider = providers.provider { [(++counter): ++counter] }
            
            def property = objects.mapProperty(Integer, Integer)
            property.set(provider)
            
            assert property.get() == [1: 2]
            assert property.get() == [3: 4]
            property.finalizeValue()
            assert property.get() == [5: 6]
            assert property.get() == [5: 6]
            
            property.set([1: 2])
            '''.stripIndent()

        when:
        fails()

        then:
        failure.assertHasCause('The value for this property is final and cannot be changed any further.')
    }

    def "task @Input property is implicitly finalized and changes ignored when task starts execution"() {
        given:
        buildFile << '''
            class SomeTask extends DefaultTask {
                @Input
                final MapProperty prop = project.objects.mapProperty(String, String)
                
                @OutputFile
                final Property outputFile = project.objects.fileProperty()
                
                @TaskAction
                void go() {
                    prop.set(['ignoredKey': 'ignoredValue'])
                    prop.put('ignoredKey', 'ignoredValue')
                    prop.putAll(['ignoredKey': 'ignoredValue'])
                    prop.empty()
                    outputFile.get().asFile.text = prop.get()
                }
            }
            
            task thing(type: SomeTask) {
                prop = ['key1': 'value1']
                outputFile = layout.buildDirectory.file('out.txt')
                doLast {
                    prop.set(['ignoredKey': 'ignoredValue'])
                }
            }
            
            afterEvaluate {
                thing.prop = ['key2': 'value2']
            }
            
            task before {
                doLast {
                    thing.prop = providers.provider { ['finalKey': 'finalValue'] }
                }
            }
            thing.dependsOn before
            
            task after {
                dependsOn thing
                doLast {
                    thing.prop = ['ignoredKey': 'ignoredValue']
                    assert thing.prop.get() == ['finalKey': 'finalValue']
                }
            }
            '''.stripIndent()

        when:
        executer.expectDeprecationWarning()
        run('after')

        then:
        file('build/out.txt').text == '[finalKey:finalValue]'
    }

    def "task ad hoc input property is implicitly finalized and changes ignored when task starts execution"() {
        given:
        buildFile << '''
            def prop = project.objects.mapProperty(String, String)
            
            task thing {
                inputs.property('prop', prop)
                prop.set(['key1': 'value1'])
                doLast {
                    prop.set(['ignoredKey': 'ignoredValue'])
                    println "prop = ${prop.get()}"
                }
            }
            '''.stripIndent()

        when:
        executer.expectDeprecationWarning()
        run('thing')

        then:
        output.contains('prop = [key1:value1]')
    }

    def "can use property with no value as optional ad hoc task input property"() {
        given:
        buildFile << """

def prop = project.objects.mapProperty(String, String)
prop.set((Map)null)

task thing {
    inputs.property("prop", prop).optional(true)
    doLast {
        println "prop = " + prop.getOrNull()
    }
}
"""

        when:
        run("thing")

        then:
        output.contains("prop = null")
    }

    @Unroll
    def "can set value for map property from DSL"() {
        given:
        buildFile << """
            verify {
                prop = ${value}
                expected = ['key1': 'value1', 'key2': 'value2']            
            }
            """.stripIndent()

        expect:
        succeeds('verify')

        where:
        value                                                          | _
        "['key1': 'value1', 'key2': 'value2']"                       | _
        "new LinkedHashMap(['key1': 'value1', 'key2': 'value2'])"    | _
        "providers.provider { ['key1': 'value1', 'key2': 'value2'] }" | _
    }

    def "can add entries to map property with string keys using index notation"() {
        given:
        buildFile << '''
            verify {
                prop.empty()
                prop['key1'] = 'value1'
                prop['key2'] = project.provider { 'value2' }
                expected = ['key1': 'value1', 'key2': 'value2']          
            }
            '''.stripIndent()

        expect:
        succeeds('verify')
    }

    def "can add entries to map property with non-string keys using index notation"() {
        given:
        buildFile << '''
            verifyInt {
                prop.empty()
                prop[1] = 111
                prop[2] = project.provider { 222 }
                expected = [1: 111, 2: 222]          
            }
            '''.stripIndent()

        expect:
        succeeds('verifyInt')
    }

    @Unroll
    def "can set value for string map property using GString keys and values"() {
        given:
        buildFile << """
            def str = "aBc"
            verify {
                prop = ${value}
                expected = ['a': 'b']            
            }
            """.stripIndent()

        expect:
        succeeds('verify')

        where:
        value                                                                                         | _
        '[ "${str.substring(0, 1)}": "${str.toLowerCase().substring(1, 2)}" ]'                        | _
        'providers.provider { [ "${str.substring(0, 1)}": "${str.toLowerCase().substring(1, 2)}" ] }' | _
    }

    def "can add entries to default value"() {
        given:
        buildFile << '''
            verify {
                prop = ['key1': 'value1']
                prop.put('key2', 'value2')
                prop.put('key3', project.provider { 'value3' })
                prop.putAll(['key4': 'value4'])
                prop.putAll(project.provider { ['key5': 'value5'] })
                expected = ['key1': 'value1', 'key2': 'value2', 'key3': 'value3', 'key4': 'value4', 'key5': 'value5']
            } 
            '''.stripIndent()
    }

    def "can add entries to empty map property"() {
        given:
        buildFile << '''
            verify {
                prop.empty()
                prop.put('key1', 'value1')
                prop.put('key2', project.provider { 'value2' })
                prop.putAll(['key3': 'value3'])
                prop.putAll(project.provider { ['key4': 'value4'] })
                expected = ['key1': 'value1', 'key2': 'value2', 'key3': 'value3', 'key4': 'value4']
            } 
            '''.stripIndent()

        expect:
        succeeds('verify')
    }

    @Unroll
    def "can add entry to string map property using GString key and value"() {
        given:
        buildFile << """
            def str = "aBc"
            verify {
                prop = ['key': 'value']
                prop.putAll(${value})
                expected = ['key': 'value', 'b': 'c']
            } 
            """.stripIndent()

        expect:
        succeeds('verify')

        where:
        value                                                                                         | _
        '["${str.toLowerCase().substring(1, 2)}": "${str.substring(2, 3)}"]'                          | _
        'providers.provider { [ "${str.toLowerCase().substring(1, 2)}": "${str.substring(2, 3)}" ] }' | _
    }

    @Unroll
    def "can add entries to string map property using GString values"() {
        given:
        buildFile << """
            def str = "aBc"
            verify {
                prop = ['key': 'value']
                prop.put(${key}, ${value})
                expected = ['key': 'value', 'a': 'b']
            } 
            """.stripIndent()

        expect:
        succeeds('verify')

        where:
        key                        | value
        '"${str.substring(0, 1)}"' | '"${str.toLowerCase().substring(1, 2)}"'
        '"${str.substring(0, 1)}"' | 'project.provider { "${str.toLowerCase().substring(1, 2)}" }'
    }

    def "reports failure to set property value using incompatible type"() {
        given:
        buildFile << '''
        task wrongValueTypeDsl {
            doLast {
                verify.prop = 123
            }
        }
        
        task wrongRuntimeKeyType {
            doLast {
                verify.prop = [123: 'value']
                verify.prop.get()
            }
        }
        
        task wrongRuntimeValueType {
            doLast {
                verify.prop = ['key': 123]
                verify.prop.get()
            }
        }
        
        task wrongPropertyTypeDsl {
            doLast {
                verify.prop = objects.property(Integer)
            }
        }
        
        task wrongPropertyTypeApi {
            doLast {
                verify.prop.set(objects.property(Integer))
            }
        }
        
        task wrongRuntimeKeyTypeDsl {
            doLast {
                verify.prop = objects.mapProperty(Integer, String)
            }
        }
        
        task wrongRuntimeValueTypeDsl {
            doLast {
                verify.prop = objects.mapProperty(String, Integer)
            }
        }
        
        task wrongRuntimeKeyTypeApi {
            doLast {
                verify.prop.set(objects.mapProperty(Integer, String))
            }
        }
        
        task wrongRuntimeValueTypeApi {
            doLast {
                verify.prop.set(objects.mapProperty(String, Integer))
            }
        }
        '''.stripIndent()

        when:
        fails('wrongValueTypeDsl')
        then:
        failure.assertHasDescription("Execution failed for task ':wrongValueTypeDsl'.")
        failure.assertHasCause('Cannot set the value of a property of type java.util.Map using an instance of type java.lang.Integer.')

        when:
        fails('wrongRuntimeKeyType')
        then:
        failure.assertHasDescription("Execution failed for task ':wrongRuntimeKeyType'.")
        failure.assertHasCause('Cannot get the value of a property of type java.util.Map with key type java.lang.String as the source contains a key of type java.lang.Integer.')

        when:
        fails('wrongRuntimeValueType')
        then:
        failure.assertHasDescription("Execution failed for task ':wrongRuntimeValueType'.")
        failure.assertHasCause('Cannot get the value of a property of type java.util.Map with value type java.lang.String as the source contains a value of type java.lang.Integer.')

        when:
        fails('wrongPropertyTypeDsl')
        then:
        failure.assertHasDescription("Execution failed for task ':wrongPropertyTypeDsl'.")
        failure.assertHasCause('Cannot set the value of a property of type java.util.Map using a provider of type java.lang.Integer.')

        when:
        fails('wrongPropertyTypeApi')
        then:
        failure.assertHasDescription("Execution failed for task ':wrongPropertyTypeApi'.")
        failure.assertHasCause('Cannot set the value of a property of type java.util.Map using a provider of type java.lang.Integer.')

        when:
        fails('wrongRuntimeKeyTypeDsl')
        then:
        failure.assertHasDescription("Execution failed for task ':wrongRuntimeKeyTypeDsl'.")
        failure.assertHasCause('Cannot set the value of a property of type java.util.Map with key type java.lang.String and value type java.lang.String using a provider with key type java.lang.Integer and value type java.lang.String.')

        when:
        fails('wrongRuntimeValueTypeDsl')
        then:
        failure.assertHasDescription("Execution failed for task ':wrongRuntimeValueTypeDsl'.")
        failure.assertHasCause('Cannot set the value of a property of type java.util.Map with key type java.lang.String and value type java.lang.String using a provider with key type java.lang.String and value type java.lang.Integer.')

        when:
        fails('wrongRuntimeKeyTypeApi')
        then:
        failure.assertHasDescription("Execution failed for task ':wrongRuntimeKeyTypeApi'.")
        failure.assertHasCause('Cannot set the value of a property of type java.util.Map with key type java.lang.String and value type java.lang.String using a provider with key type java.lang.Integer and value type java.lang.String.')

        when:
        fails('wrongRuntimeValueTypeApi')
        then:
        failure.assertHasDescription("Execution failed for task ':wrongRuntimeValueTypeApi'.")
        failure.assertHasCause('Cannot set the value of a property of type java.util.Map with key type java.lang.String and value type java.lang.String using a provider with key type java.lang.String and value type java.lang.Integer.')
    }

    def "later entries replace earlier entries"() {
        given:
        buildFile << '''
            verify.prop = ['key': 'value']

            task replacingPut {
                doLast {
                    verify.prop.put('key', 'newValue')
                    verify.expected = ['key': 'newValue']
                }
            }
            
            task replacingPutWithProvider {
                doLast {
                    verify.prop.put('key', project.provider { 'newValue' })
                    verify.expected = ['key': 'newValue']
                }
            }
            
            task replacingPutAll {
                doLast {
                    verify.prop.putAll(['key': 'newValue', 'otherKey': 'otherValue'])
                    verify.expected = ['key': 'newValue', 'otherKey': 'otherValue']
                }
            }
            
            task replacingPutAllWithProvider {
                doLast {
                    verify.prop.putAll(project.provider { ['key': 'newValue', 'otherKey': 'otherValue'] })
                    verify.expected = ['key': 'newValue', 'otherKey': 'otherValue']
                }
            }
        '''.stripIndent()

        expect:
        succeeds('replacingPut', 'verify')
        and:
        succeeds('replacingPutWithProvider', 'verify')
        and:
        succeeds('replacingPutAll', 'verify')
        and:
        succeeds('replacingPutAllWithProvider', 'verify')
    }

    def "puts to non-defined property do nothing"() {
        given:
        buildFile << '''
            verify {
                prop = null
                prop.put('key1', 'value1')
                prop.put('key2', project.provider { 'value2' })
                prop.putAll(['key3': 'value3'])
                prop.putAll(project.provider { ['key4': 'value4'] })
                expected = null
            }   
            '''.stripIndent()

        expect:
        succeeds('verify')
    }

    def "reasonable message when trying to add an entry with a null key to a map property"() {
        given:
        buildFile << '''
            verify {
                prop.put(null, 'value')
            }
            '''.stripIndent()

        expect:
        def failure = fails('verify')
        failure.assertHasCause('Cannot add an entry with a null key to a property of type Map.')
    }

    def "reasonable message when trying to add an entry with a null value to a map property"() {
        given:
        buildFile << '''
            verify {
                prop.put('key', null)
            }
            '''.stripIndent()

        expect:
        def failure = fails('verify')
        failure.assertHasCause('Cannot add an entry with a null value to a property of type Map.')
    }

    def "has no value when providing a null entry to a map property"() {
        given:
        buildFile << '''
            verify {
                prop.put('key', project.provider { null })
                expected = null
            }   
            '''.stripIndent()

        expect:
        succeeds('verify')
    }

    def "has no value when providing a null map to a map property"() {
        given:
        buildFile << '''
            verify {
                prop.putAll(project.provider { null })
                expected = null
            }   
            '''.stripIndent()

        expect:
        succeeds('verify')
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy