#!groovy

//*** job setup */
properties([
            buildDiscarder(logRotator(artifactDaysToKeepStr: '',
                    artifactNumToKeepStr: '',
                    daysToKeepStr: '',
                    numToKeepStr: '50')),
            parameters([
                        string(defaultValue: 'frm2/tango/entangle',
                                description: '', name: 'GERRIT_PROJECT'),
                        string(defaultValue: 'refs/heads/master',
                                description: '', name: 'GERRIT_BRANCH'),
                        string(defaultValue: 'refs/heads/master',
                                description: '', name: 'GERRIT_REFSPEC'),
                        choice(choices: '''\
                            patchset-created
                            ref-updated
                            change-merged''',
                            description: '', name: 'GERRIT_EVENT_TYPE')]),
            [$class: 'ScannerJobProperty', doNotScan: false],
            [$class: 'RebuildSettings', autoRebuild: false, rebuildDisabled: false],
            [$class: 'ThrottleJobProperty', categories: [],
             limitOneJobWithMatchingParams: false,
             maxConcurrentPerNode: 0,
             maxConcurrentTotal: 4,
             paramsToUseForLimit: '',
             throttleEnabled: false,
             throttleOption: 'project'],
            pipelineTriggers([gerrit(commentTextParameterMode: 'PLAIN',
                    commitMessageParameterMode: 'PLAIN',
                    customUrl: '',
                    gerritProjects: [
                                     [
                                      pattern: 'frm2/tango/entangle',
                                      compareType: 'PLAIN',
                                      disableStrictForbiddenFileVerification: false,
                                      branches: [[compareType: 'PLAIN', pattern: 'master']],
                                      ]],
                    serverName: 'defaultServer',
                    triggerOnEvents: [
                                      patchsetCreated(excludeDrafts: false,
                                              excludeNoCodeChange: false,
                                              excludeTrivialRebase: false),
                                      changeMerged(),
                                      commentAddedContains('@recheck')
                                      ]
                    )])
            ])


// ********************************/
this.testenv = null

this.verifyresult = [:]

    // ************* Function defs ***/

def publishGerrit(name, value) {
    def map = [(-1): "FAILED", 0: "RUNNING", 1: "SUCCESSFUL"]
    gerritPostCheck(["jenkins:${name}":map[value]])
}


def runBaseClassGen() {
    verifyresult.put('baseclassdoc', 0)
    publishGerrit('baseclassdoc', verifyresult['baseclassdoc'])
    def status = "OK"
    try {
        this.testenv.inside() {
            sh '''\
#!/bin/bash
. /home/jenkins/py3entangle/bin/activate

# check that baseclass code/docs has been regenerated if necessary
make baseclass-code >/dev/null
make baseclass-docs-gen >/dev/null
if ! git diff --exit-code; then
    echo "Build step failed: Files not regenerated after XMI change"
    exit 1
fi
            '''
        } // inside
        verifyresult.put('baseclassdoc', 1)
    } catch (all) {
        verifyresult.put('baseclassdoc', -1)
        status = 'FAILURE'
    }
    publishGerrit('baseclassdoc', verifyresult['baseclassdoc'])

    if (status == 'FAILURE') {
        throw new Exception('Failure in baseclassdoc')
    }
}

def runChecks() {
    def status = "OK"

    verifyresult.put('pylint', 0)
    publishGerrit('pylint', verifyresult['pylint'])
    try{
        withCredentials([string(credentialsId: 'GERRITHTTP', variable: 'GERRITHTTP')]) {
            this.testenv.inside() {
                sh '''\
#!/bin/bash
. /home/jenkins/py3entangle/bin/activate

pip install -r requirements.txt &> pip_checks.txt
pip install -r requirements-dev.txt &>> pip_checks.txt

# run pylint
rm -f pylint_*.txt
set +e

PYFILESCHANGED=$(~/tools2/bin/changedfiles --py)
if [[ -n "$PYFILESCHANGED" ]] ; then
    set -o pipefail
    PYTHONPATH=.:${PYTHONPATH} pylint --rcfile=./pylintrc $PYFILESCHANGED | tee pylint_all.txt
    res=$?
    set +o pipefail
else
    echo 'no python files changed'
    res=$?
fi

set -e
/home/jenkins/tools2/bin/pylint2gerrit
exit $res
                '''
            }// inside
        } // withCredentials
        verifyresult.put('pylint', 1)
    } catch (all) {
        verifyresult.put('pylint', -1)
        status = 'FAILURE'
    }
    publishGerrit('pylint', verifyresult['pylint'])

    if (status == 'FAILURE') {
        error('Failure in pylint')
    }
}  // runChecks

def runTests(venv, pyver, cov) {
    def configtext = """
addopts = --junit-xml=pytest-${pyver}.xml
  --junit-prefix=$pyver"""
    if (cov) {
        configtext += """
  --cov
  --cov-config=.coveragerc
  --cov-report=html:cov-$pyver
  --cov-report=term
"""
    }
    writeFile file: 'pytest_ini.add'+pyver, text: configtext
    sh """
    [ -f pytest.ini ] || echo "[pytest]" > pytest.ini
    cat  pytest.ini > pytest${pyver}.ini
    cat pytest_ini.add${pyver} >> pytest${pyver}.ini"""

    def status = "OK"
    verifyresult.put(pyver, 0)
    publishGerrit('pytest-'+pyver, verifyresult[pyver])
    // no need to always pull mysql and tango
    def mysqltango=docker.image('docker.ictrl.frm2.tum.de:5443/docker_proxy/tangocs/mysql')
    def tangodb=docker.image('docker.ictrl.frm2.tum.de:5443/docker_proxy/tangocs/tango-cs:9')

    try {
        timeout(5) {
            withEnv(["VENV=$venv"]) {
                mysqltango.withRun('-e MYSQL_ROOT_PASSWORD=root') { mysql ->
                    tangodb.withRun("-e ORB_PORT=10000 -e TANGO_HOST=127.0.0.1:10000 -e MYSQL_HOST=db:3306 -e MYSQL_USER=tango -e MYSQL_PASSWORD=tango -e MYSQL_DATABASE=tango --link ${mysql.id}:db "){ tangocs ->
                        this.testenv.inside("--link ${tangocs.id}:tango -e TANGO_HOST=tango:10000") {


                sh """\
#!/bin/bash
. /home/jenkins/${VENV}/bin/activate
pip install -r requirements.txt &> pip-test_${pyver}.txt
pip install -r requirements-dev.txt &>> pip-test_${pyver}.txt

/home/jenkins/${VENV}/bin/wait-for-tangodb.py


pytest -c pytest${pyver}.ini -v test
		  """

                        }
                    }
                }

verifyresult.put(pyver, 1)
archiveArtifacts([allowEmptyArchive: true,
                  artifacts: "cov-${pyver}/*"])
publishHTML([allowMissing: true,
             alwaysLinkToLastBuild: false,
             keepAll: true,
             reportDir: "cov-$pyver/",
             reportFiles: 'index.html',
             reportName: "Coverage ($pyver)"])

            }
        }
    } catch (all) {
        currentBuild.result = 'FAILURE'
        status = 'FAILURE'
        verifyresult.put(pyver, -1)
    }
    publishGerrit('pytest-'+pyver, verifyresult[pyver])

    junit([allowEmptyResults: true,
           keepLongStdio: true,
           testResults: "pytest-${pyver}.xml"])

    if (status == 'FAILURE') {
        error('Failure in test with ' + pyver)
    }
} // pytest

def runDocTest() {
    verifyresult.put('doc', 0)
    publishGerrit('doc', verifyresult['doc'])
    def status = "OK"
    try {
        this.testenv.inside() {
            sh '''\
#!/bin/bash
. /home/jenkins/py3entangle/bin/activate
touch doc/warnings.txt
pip install -r requirements.txt &> pip-doctest.txt
# check documentation coverage
if ! make covcheck; then
    echo "Build step failed: Found undocumented or unimplemented classes"
    exit 1
fi

doc_changed=$(git diff --name-status `git merge-base HEAD HEAD\\^` | sed -e '/^D/d' | sed -e 's,.\t,,' | grep doc)
cd doc

if [[ -n "$doc_changed" ]]; then
    if ! make html SPHINXOPTS="-w warnings.txt"; then
        echo "Build step failed: Sphinx build failed"
        exit 1
    fi

# check for Sphinx warnings, except the normal one about overridden directive
    if grep -v "already registered" warnings.txt; then
        echo "Build step failed: Doc build produced Sphinx warnings"
        exit 1
    fi
fi
            '''
        } //inside
        publishHTML([allowMissing: true,
                     alwaysLinkToLastBuild: true,
                     keepAll: true,
                     reportDir: 'doc/_build/html',
                     reportFiles: 'index.html',
                     reportName: 'Entangle Doc (test build)'])

        verifyresult.put('doc', 1)
    } catch (all) {
        verifyresult.put('doc', -1)
        status = 'FAILURE'
    }
    publishGerrit('doc', verifyresult['doc'])

    if (status == 'FAILURE') {
        error('Failure in doc checks')
    }
}  // doctest

// ************* End Function defs ***/

// ************* Start main script ***/

node('dockerhost') {
stage(name: 'checkout code: ' + GERRIT_PROJECT) {
    deleteDir()
    this.testenv = docker.image('docker.ictrl.frm2.tum.de:5443/jenkins/entangle-jenkins:bookworm')
    this.testenv.pull()
    checkout(changelog: true, poll: false,
        scm: [$class: 'GitSCM',
            branches: [[name: "$GERRIT_BRANCH"]],
            doGenerateSubmoduleConfigurations: false, submoduleCfg: [],
            userRemoteConfigs: [
                [refspec: GERRIT_REFSPEC,
                 // use local mirror via git
                 url: 'file:///home/git/'+GERRIT_PROJECT
                 // use gerrit directly
                 //credentialsId: 'jenkinsforge',
                 //url: 'ssh://forge.frm2.tum.de:29418/' + GERRIT_PROJECT,
                ]],
            extensions:[
                [$class: 'CleanCheckout'],
                [$class: 'hudson.plugins.git.extensions.impl.BuildChooserSetting',
                 buildChooser: [$class: "com.sonyericsson.hudson.plugins.gerrit.trigger.hudsontrigger.GerritTriggerBuildChooser"]],
            ]
        ])
    sh '''git describe'''
}

stage(name: '(Re-)generate baseclasses') {
    runBaseClassGen()
}


parallel checks: {
    stage(name: 'Checks') {
        try {
            runChecks()
        } finally {
            recordIssues([enabledForFailure: true,
                  ignoreQualityGate: true,
                  tools: [pyLint(pattern: 'pylint_*.txt')],
                  unhealthy: 2,
                  healthy: 1,
                  failedTotalAll: 1])
        }
    }
}, test_python3: {
    stage(name: 'Python3 tests') {
        runTests('py3entangle', 'python3', true)
    }
}, test_docs: {
    stage(name: 'Test docs') {
        try {
            runDocTest()
        } finally {
            recordIssues([
                  enabledForFailure: true,
                  ignoreQualityGate: true,
                  tools: [groovyScript(pattern: 'doc/warnings.txt',
                                       id: 'sphinx-errors',
                                       name: 'Sphinx Errors',
                                       parserId: 'sphinx-error',
                                       reportEncoding: 'UTF-8'),
                          groovyScript(pattern: 'doc/warnings.txt',
                                       id: 'sphinx-warnings',
                                       name: 'Sphinx Warnings',
                                       parserId: 'sphinx-warning',
                                       reportEncoding: 'UTF-8')
                          ],
                  unhealthy: 2,
                  healthy: 1,
                  // fail if there are errors, allow 1 warning
                  failedTotalHigh: 1, failedTotalLow: 2 ])
        }
    }
},
failFast: false

/*** set final vote **/
setGerritReview()
}
