Dynamically run selected tests

Moving forward with making deployment take less time, one huge improvement is to make use of Salesforce feature to run selected tests. One big change when doing this is that now you will need to have at least 75% code coverage in each class that you are deploying and at least 1% in each trigger that is part of that package, against the normal deployments where you need to have at least an overall 75% code coverage.

We will be using the script that was explained in my previous post (Dynamic package.xml generation) and enhancing it to know based on what is being part of the package.xml which tests should be run. Note that to do this as cleaner as possible, you need to have a naming convention for test classes. In our case is [name of the class] + Tests.

Let’s take a look at the script:

#/usr/bin/env bash
# -lcommit builds last commit
# -prevrsa last commit to master

#read command line args
while getopts l:p: option
do
        case "${option}"
        in
                l) LCOMMIT=${OPTARG};;
                p) PREVRSA=${OPTARG};;
        esac
done

echo Last Commit: $LCOMMIT
echo Previous Commit: $PREVRSA

DIRDEPLOY=build/deploy
if [ -d "$DIRDEPLOY" ]; then
    echo Removing deploy folder
    rm -rf "$DIRDEPLOY"
fi
mkdir -p $DIRDEPLOY/src
cd src
echo changing directoy to src
cp package.xml{,.bak} &&
echo Backing up package.xml to package.xml.bak

NEWPKGXML=$(<packageBase.xml) echo $NEWPKGXML > package.xml
echo List of changes
echo DIFF: `git diff-tree --no-commit-id --name-only --diff-filter=ACMRTUXB -t -r $PREVRSA $LCOMMIT`
printf "\n"
echo Moving files, generating package.xml and test list
git diff-tree --no-commit-id --name-only --diff-filter=ACMRTUXB -t -r $PREVRSA $LCOMMIT | \
while read -r CFILE; do
        if [[ $CFILE == *"src/"*"."* ]]
        then
            TEMPCFILE=${CFILE#*src/}
                tar cf - "$TEMPCFILE"* | (cd ../$DIRDEPLOY/src; tar xf -)
        fi
        if [[ $CFILE == *"-meta.xml" ]]
        then
                ADDFILE=${CFILE#*src/}
                ADDFILE="${ADDFILE%-meta.xml*}"
                tar cf - $ADDFILE | (cd ../$DIRDEPLOY/src; tar xf -)
        fi
        if [[ $CFILE == *"/aura/"*"."* ]]
        then
                DIR=$(dirname "$CFILE")
                TEMPDIR=${DIR#*src/}
                tar cf - $TEMPDIR | (cd ../$DIRDEPLOY/src; tar xf -)
        fi

        case "$CFILE"
        in
                *.snapshot*) TYPENAME="AnalyticSnapshot";;
                *.cls*) TYPENAME="ApexClass";;
                *.component*) TYPENAME="ApexComponent";;
                *.page*) TYPENAME="ApexPage";;
                *.trigger*) TYPENAME="ApexTrigger";;
                *.approvalProcess*) TYPENAME="ApprovalProcess";;
                *.assignmentRules*) TYPENAME="AssignmentRules";;
                */aura/*) TYPENAME="AuraDefinitionBundle";;
                *.autoResponseRules*) TYPENAME="AutoResponseRules";;
                *.community*) TYPENAME="Community";;
                */applications*.app*) TYPENAME="CustomApplication";;
                *.customApplicationComponent*) TYPENAME="CustomApplicationComponent";;
                *.labels*) TYPENAME="CustomLabels";;
                *.md*) TYPENAME="CustomMetadata";;
                */objects/*__*__c.object*)
                        TYPENAME="UNKNOWN TYPE" # We don't want objects from managed packages to be deployed
                    ;;
                */objects*.object*) TYPENAME="CustomObject";;
                *.objectTranslation*) TYPENAME="CustomObjectTranslation";;
                *.weblink*) TYPENAME="CustomPageWebLink";;
                *.customPermission*) TYPENAME="CustomPermission";;
                *.tab*) TYPENAME="CustomTab";;
                */documents/*.*) TYPENAME="Document";;
                *.email*) TYPENAME="EmailTemplate";;
                */email/*-meta.xml) TYPENAME="EmailTemplate";;
                *.escalationRules*) TYPENAME="EscalationRules";;
                *.globalValueSet*) TYPENAME="GlobalValueSet";;
                *.globalValueSetTranslation*) TYPENAME="GlobalValueSetTranslation";;
                *.group*) TYPENAME="Group";;
                *.homePageComponent*) TYPENAME="HomePageComponent";;
                *.homePageLayout*) TYPENAME="HomePageLayout";;
                *.layout*) TYPENAME="Layout";;
                *.letter*) TYPENAME="Letterhead";;
                *.permissionset*) TYPENAME="PermissionSet";;
                *.cachePartition*) TYPENAME="PlatformCachePartition";;
                *.profile*) TYPENAME="Profile";;
                *.reportType*) TYPENAME="ReportType";;
                *.role*) TYPENAME="Role";;
                *OrgPreference.settings*) TYPENAME="UNKNOWN TYPE";;
                *.settings*) TYPENAME="Settings";;
                */standardValueSets*.standardValueSet*) TYPENAME="StandardValueSet";;
                *.standardValueSetTranslation*) TYPENAME="StandardValueSetTranslation";;
                *.resource*) TYPENAME="StaticResource";;
                *.translation*) TYPENAME="Translations";;
                *.workflow*) TYPENAME="Workflow";;
                *) TYPENAME="UNKNOWN TYPE";;
        esac

        if [[ "$TYPENAME" != "UNKNOWN TYPE" ]]
        then

                case "$CFILE"
                in
                        src/email/*)  ENTITY="${CFILE#src/email/}";;
                        src/documents/*)  ENTITY="${CFILE#src/documents/}";;
                        src/aura/*)  ENTITY="${CFILE#src/aura/}" ENTITY="${ENTITY%/*}";;
                        *) ENTITY=$(basename "$CFILE");;
                esac

                if [[ $ENTITY == *"-meta.xml" ]]
                then
                        ENTITY="${ENTITY%%.*}"
                        ENTITY="${ENTITY%-meta*}"
                else
                        ENTITY="${ENTITY%.*}"
                fi

                if grep -Fq "<name>$TYPENAME</name>" package.xml
                then
                        xmlstarlet ed -L -s "/Package/types[name='$TYPENAME']" -t elem -n members -v "$ENTITY" package.xml
                else
                        xmlstarlet ed -L -s /Package -t elem -n types -v "" package.xml
                        xmlstarlet ed -L -s '/Package/types[not(*)]' -t elem -n name -v "$TYPENAME" package.xml
                        xmlstarlet ed -L -s "/Package/types[name='$TYPENAME']" -t elem -n members -v "$ENTITY" package.xml
                fi

                if [[ "$TYPENAME" == "ApexClass" ]]
                then
                    shopt -s nocasematch # making the comparison case insensitive
                    case "$ENTITY"
                    in
                        *Test*) TESTNAME="$ENTITY";;
                        CaseDAO) TESTNAME="CaseDAO_Test";;
                        ContractDAO) TESTNAME="ContractDAO_Test";;
                        *) TESTNAME="${ENTITY}Tests";;
                    esac
                    shopt -u nocasematch # making the comparison back to case sensitive
                        if [[ "$ENTITY" != "UNKNOWN TYPE" ]]
                        then
                            if ! grep -Fq "<runTest>$TESTNAME</runTest>" ../build/build.xml
                            then
                                xmlstarlet ed -L -s "/project/target[@name='dynamicTests']/sf:deploy" -t elem -n runTest -v "$TESTNAME" ../build/build.xml
                            fi
                        fi
                fi
                if [[ "$TYPENAME" == "ApexTrigger" ]]
                then
                    echo need to build a logic for triggers
                fi
        fi
done

echo Cleaning up Package.xml
xmlstarlet ed -L -i /Package -t attr -n xmlns -v "http://soap.sforce.com/2006/04/metadata" package.xml

echo ====FINAL PACKAGE.XML=====
cat package.xml
tar cf - package.xml | (cd ../$DIRDEPLOY/src; tar xf -)
echo ====FINAL BUILD.XML=====
xmlstarlet sel -t -c "/project/target[@name='dynamicTests']/sf:deploy" ../build/build.xml

Let’s analyse what this script is doing, I will jump to the selected tests section ( line 132 onwards). If you need information on the rest of the script please read my previous post.

if [[ "$TYPENAME" == "ApexClass" ]]
                then
                    shopt -s nocasematch # making the comparison case insensitive
                    case "$ENTITY"
                    in

Here we are only looking at the Apex classes. We are using shopt command to make the case comparison case insensitive, so that is a class has a name with test instead of Test it will treat them the same way.

*Test*) TESTNAME="$ENTITY";;

With this line we are saying that if the class that changed is a test class, then take it as it is.

CaseDAO) TESTNAME="CaseDAO_Test";;
ContractDAO) TESTNAME="ContractDAO_Test";;

In this line we had to introduce an “exemption” to these classes as they are not following the naming convention. Yes, we should go ahead and change them, but that will go to our technical debt and can be fixed later.

shopt -u nocasematch # making the comparison back to case sensitive
                        if [[ "$ENTITY" != "UNKNOWN TYPE" ]]
                        then
                            if ! grep -Fq "<runTest>$TESTNAME</runTest>" ../build/build.xml
                            then
                                xmlstarlet ed -L -s "/project/target[@name='dynamicTests']/sf:deploy" -t elem -n runTest -v "$TESTNAME" ../build/build.xml
                            fi
                        fi
                fi

We are reverting the command shopt to be case sensitive again. Besides that, we are checking if we identified a test to be added to the file (“$ENTITY” != “UNKNOWN TYPE”), check that test is not already there, and add it. Take into account that we are adding this tests to the build.xml file. I believe the best option is to actually add it to a specific file, but at this time I couldn’t find a way to make the ANT task to take the list from a separate file.

The last part of the script is to print the resulting test list.

Feel free to comment any enhancement that you made to this script.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s