/*
 * Copyright 2013-2026, Seqera Labs
 *
 * 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 nextflow.executor

import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths

import nextflow.Session
import nextflow.processor.TaskArrayRun
import nextflow.processor.TaskConfig
import nextflow.processor.TaskProcessor
import nextflow.processor.TaskRun
import spock.lang.Specification
import spock.lang.Unroll

/**
 *
 * @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
 */
class LsfExecutorTest extends Specification {

    def createExecutor(config) {
        Spy(LsfExecutor) {
            getConfig() >> config
        }
    }

    def createExecutor() {
        createExecutor(new ExecutorConfig([:]))
    }

    def testCommandLine() {

        when:
        def executor = createExecutor()
        then:
        executor.getSubmitCommandLine(Mock(TaskRun), null) == ['bsub']

    }

    def testMemDirectiveMemUnit() {
        given:
        def WORK_DIR = Paths.get('/work/dir')
        def executor = createExecutor()
        executor.getSession() >> Mock(Session)
        and:
        def task = Mock(TaskRun)
        task.workDir >> WORK_DIR

        when:
        def result = executor.getDirectives(task, [])
        then:
        1 * executor.getJobNameFor(task) >> 'foo'
        _ * task.config >> new TaskConfig(memory: '10MB')
        then:
        result == ['-o', '/work/dir/.command.log',
                   '-M', '10MB',
                   '-R', 'select[mem>=10MB] rusage[mem=10MB]',
                   '-J', 'foo']
    }

    def testReserveMemPerTask() {
        given:
        def WORK_DIR = Paths.get('/work/dir')
        def executor = createExecutor()
        executor.getSession() >> Mock(Session)
        and:
        def task = Mock(TaskRun)
        task.workDir >> WORK_DIR

        when:
        executor.@perJobMemLimit = true
        def result = executor.getDirectives(task, [])
        then:
        1 * executor.getJobNameFor(task) >> 'foo'
        _ * task.config >> new TaskConfig(memory: '10MB', cpus: 2)
        then:
        result == ['-o', '/work/dir/.command.log',
                   '-n', '2',
                   '-R', 'span[hosts=1]',
                   '-M', '10MB',
                   '-R', 'select[mem>=10MB] rusage[mem=10MB]',
                   '-J', 'foo']
    }

    def testReserveMemPerTask2() {
        given:
        def WORK_DIR = Paths.get('/work/dir')
        def executor = createExecutor()
        executor.getSession() >> Mock(Session)
        and:
        def task = Mock(TaskRun)
        task.workDir >> WORK_DIR

        when:
        executor.@perJobMemLimit = true
        executor.@perTaskReserve = true
        def result = executor.getDirectives(task, [])
        then:
        1 * executor.getJobNameFor(task) >> 'foo'
        _ * task.config >> new TaskConfig(memory: '10MB', cpus: 2)
        then:
        result == ['-o', '/work/dir/.command.log',
                   '-n', '2',
                   '-R', 'span[hosts=1]',
                   '-M', '10MB',
                   '-R', 'select[mem>=10MB] rusage[mem=5MB]',
                   '-J', 'foo']

    }

    def 'test job script headers' () {

        setup:
        def executor = createExecutor()
        executor.session = new Session()

        def proc = Mock(TaskProcessor)
        proc.getName() >> 'task'

        def task = new TaskRun()
        task.processor = proc
        task.workDir = Paths.get('/scratch')
        task.name = 'mapping hola'

        when:
        task.config = new TaskConfig()
        task.config.queue = 'bsc_ls'
        task.config.clusterOptions = "-x 1 -R \"span[ptile=2]\""
        task.config.cpus = '2'
        task.config.time = '1h 30min'
        task.config.memory = '8GB'
        then:
        executor.getHeaders(task) == '''
                #BSUB -o /scratch/.command.log
                #BSUB -q bsc_ls
                #BSUB -n 2
                #BSUB -R "span[hosts=1]"
                #BSUB -W 01:30
                #BSUB -M 4096MB
                #BSUB -R "select[mem>=8192MB] rusage[mem=8192MB]"
                #BSUB -J nf-mapping_hola
                #BSUB -x 1
                #BSUB -R "span[ptile=2]"
                '''
                .stripIndent().leftTrim()

        when:
        task.config = new TaskConfig()
        task.config.queue = 'bsc_ls'
        task.config.clusterOptions = ['-x 1', '-R "span[ptile=2]"']
        task.config.cpus = '2'
        task.config.time = '1h 30min'
        task.config.memory = '8GB'
        then:
        executor.getHeaders(task) == '''
                #BSUB -o /scratch/.command.log
                #BSUB -q bsc_ls
                #BSUB -n 2
                #BSUB -R "span[hosts=1]"
                #BSUB -W 01:30
                #BSUB -M 4096MB
                #BSUB -R "select[mem>=8192MB] rusage[mem=8192MB]"
                #BSUB -J nf-mapping_hola
                #BSUB -x 1
                #BSUB -R "span[ptile=2]"
                '''
            .stripIndent().leftTrim()

        when:
        task.config = new TaskConfig()
        task.config.queue = 'alpha'
        task.config.cpus = 1
        then:
        executor.getHeaders(task) == '''
                #BSUB -o /scratch/.command.log
                #BSUB -q alpha
                #BSUB -J nf-mapping_hola
                '''
                .stripIndent().leftTrim()

        when:
        task.config = new TaskConfig()
        task.config.queue = 'alpha'
        task.config.cpus = 1
        task.config.time = '1min'
        task.config.memory = '10MB'
        then:
        executor.getHeaders(task) == '''
                #BSUB -o /scratch/.command.log
                #BSUB -q alpha
                #BSUB -W 00:01
                #BSUB -M 10MB
                #BSUB -R "select[mem>=10MB] rusage[mem=10MB]"
                #BSUB -J nf-mapping_hola
                '''
                .stripIndent().leftTrim()

        when:
        task.config = new TaskConfig()
        task.config.queue = 'gamma'
        task.config.cpus = 1
        task.config.time = '4h'
        task.config.memory = '200MB'
        then:
        executor.getHeaders(task) == '''
                #BSUB -o /scratch/.command.log
                #BSUB -q gamma
                #BSUB -W 04:00
                #BSUB -M 200MB
                #BSUB -R "select[mem>=200MB] rusage[mem=200MB]"
                #BSUB -J nf-mapping_hola
                '''
                .stripIndent().leftTrim()

        when:
        task.config = new TaskConfig()
        task.config.queue = 'gamma'
        task.config.cpus = 4
        task.config.memory = '2GB'
        then:
        executor.getHeaders(task) == '''
                #BSUB -o /scratch/.command.log
                #BSUB -q gamma
                #BSUB -n 4
                #BSUB -R "span[hosts=1]"
                #BSUB -M 512MB
                #BSUB -R "select[mem>=2048MB] rusage[mem=2048MB]"
                #BSUB -J nf-mapping_hola
                '''
                .stripIndent().leftTrim()

        when:
        task.config = new TaskConfig()
        task.config.queue = 'gamma'
        task.config.cpus = 4
        task.config.memory = '2GB'
        task.config.time = '1d'
        then:
        executor.getHeaders(task) == '''
                #BSUB -o /scratch/.command.log
                #BSUB -q gamma
                #BSUB -n 4
                #BSUB -R "span[hosts=1]"
                #BSUB -W 24:00
                #BSUB -M 512MB
                #BSUB -R "select[mem>=2048MB] rusage[mem=2048MB]"
                #BSUB -J nf-mapping_hola
                '''
                .stripIndent().leftTrim()

        when:
        task.config = new TaskConfig()
        task.config.queue = 'gamma'
        task.config.cpus = 8
        task.config.memory = '2GB'
        task.config.time = '2d'
        then:
        executor.getHeaders(task) == '''
                #BSUB -o /scratch/.command.log
                #BSUB -q gamma
                #BSUB -n 8
                #BSUB -R "span[hosts=1]"
                #BSUB -W 48:00
                #BSUB -M 256MB
                #BSUB -R "select[mem>=2048MB] rusage[mem=2048MB]"
                #BSUB -J nf-mapping_hola
                '''
                .stripIndent().leftTrim()

        when:
        task.config = new TaskConfig()
        task.config.queue = 'delta'
        task.config.memory = '2GB'
        task.config.time = '2d 12h 5m'
        then:
        executor.getHeaders(task) == '''
                #BSUB -o /scratch/.command.log
                #BSUB -q delta
                #BSUB -W 60:05
                #BSUB -M 2048MB
                #BSUB -R "select[mem>=2048MB] rusage[mem=2048MB]"
                #BSUB -J nf-mapping_hola
                '''
                .stripIndent().leftTrim()

        when: 'with job array'
        def taskArray = Mock(TaskArrayRun) {
            config >> new TaskConfig()
            name >> task.name
            workDir >> task.workDir
            getArraySize() >> 5
        }
        then:
        executor.getHeaders(taskArray) == '''
                #BSUB -o /dev/null
                #BSUB -J "nf-mapping_hola[1-5]"
                '''
                .stripIndent().leftTrim()

    }

    def testDiskResources() {
        given:
        def config = new TaskConfig(clusterOptions: [], disk: '10GB')
        def WORKDIR = Paths.get('/my/work')
        def executor = createExecutor()
        executor.getSession() >> Mock(Session)
        and:
        def task = Mock(TaskRun)

        when:
        def result = executor.getDirectives(task)
        then:
        task.workDir >> WORKDIR
        task.config >> config
        task.name >> 'foo'
        and:
        result.join(' ') == "-o $WORKDIR/.command.log -R select[tmp>=10240MB] rusage[tmp=10240MB] -J nf-foo"
    }

    def testPerJobMemLimit() {
        setup:
        // mock process
        def proc = Mock(TaskProcessor)
        // process name
        proc.getName() >> 'task'

        // task object
        def task = new TaskRun()
        task.processor = proc
        task.workDir = Paths.get('/scratch')
        task.name = 'mapping hola'

        // config
        task.config = new TaskConfig()
        task.config.queue = 'bsc_ls'
        task.config.cpus = 4
        task.config.memory = '8GB'

        when:
        // LSF executor
        def executor = createExecutor()
        executor.session = new Session()

        then:
        executor.getHeaders(task) == '''
                #BSUB -o /scratch/.command.log
                #BSUB -q bsc_ls
                #BSUB -n 4
                #BSUB -R "span[hosts=1]"
                #BSUB -M 2048MB
                #BSUB -R "select[mem>=8192MB] rusage[mem=8192MB]"
                #BSUB -J nf-mapping_hola
                '''
                .stripIndent().leftTrim()

    }

    def 'should use per job mem limit' () {
        given:
        // mock process
        def proc = Mock(TaskProcessor)
        // process name
        proc.getName() >> 'task'

        // task object
        def task = new TaskRun()
        task.processor = proc
        task.workDir = Paths.get('/scratch')
        task.name = 'mapping hola'

        // config
        task.config = new TaskConfig()
        task.config.queue = 'bsc_ls'
        task.config.cpus = 4
        task.config.memory = '8GB'

        when:
        // LSF executor
        def config = new ExecutorConfig(perJobMemLimit: true)
        def executor = createExecutor(config)
        executor.register()

        then:
        executor.getHeaders(task) == '''
                #BSUB -o /scratch/.command.log
                #BSUB -q bsc_ls
                #BSUB -n 4
                #BSUB -R "span[hosts=1]"
                #BSUB -M 8192MB
                #BSUB -R "select[mem>=8192MB] rusage[mem=8192MB]"
                #BSUB -J nf-mapping_hola
                '''
                .stripIndent().leftTrim()
    }

    def testWorkDirWithBlanks() {

        given:
        // LSF executor
        def executor = createExecutor()
        executor.session = new Session()
        and:
        // mock process
        def proc = Mock(TaskProcessor)
        // process name
        proc.getName() >> 'task'

        // task object
        def task = new TaskRun()
        task.processor = proc
        task.workDir = Paths.get('/scratch/some data/path')
        task.name = 'mapping hola'

        when:
        task.config = new TaskConfig()
        // config
        task.config.queue = 'bsc_ls'
        task.config.clusterOptions = "-x 1 -R \"span[ptile=2]\""
        task.config.cpus = '2'
        task.config.time = '1h 30min'
        task.config.memory = '8GB'


        then:
        executor.getHeaders(task) == '''
                #BSUB -o "/scratch/some data/path/.command.log"
                #BSUB -q bsc_ls
                #BSUB -n 2
                #BSUB -R "span[hosts=1]"
                #BSUB -W 01:30
                #BSUB -M 4096MB
                #BSUB -R "select[mem>=8192MB] rusage[mem=8192MB]"
                #BSUB -J nf-mapping_hola
                #BSUB -x 1
                #BSUB -R "span[ptile=2]"
                '''
                .stripIndent().leftTrim()


    }

    def testParseJobId() {

        when:
        // executor stub object
        def executor = createExecutor()
        then:
        executor.parseJobId( 'Job <2329803> is submitted to default queue <research-rh6>.' ) == '2329803'

    }

    def testKillCommand() {
        when:
        // executor stub object
        def executor = createExecutor()
        then:
        executor.killTaskCommand('12345').join(' ') == 'bkill -d 12345'

    }

    def testQstatCommand() {

        setup:
        def executor = createExecutor()
        def text =
                """\
                JOBID   USER    STAT  QUEUE      FROM_HOST   EXEC_HOST   JOB_NAME   SUBMIT_TIME
                5085109 pluskal RUN   normal     it-c01b13   it-c01b11   /lab/solexa_weng/xtal_data/Data_processing/Tomas_Pluskal/20181031_KLR1_autobuild/AutoBuild_run_1_/TEMP0/RUN_FILE_4 Oct 31 13:00
                5085585 pluskal RUN   normal     tak         it-c05b12   phenix.autobuild autobuild_config.eff Oct 31 14:05
                5085604 pluskal PEND  normal     it-c05b12   it-c05b12   /lab/solexa_weng/xtal_data/Data_processing/Tomas_Pluskal/20181031_KLR1_autobuild_not_in_place/AutoBuild_run_1_/TEMP0/RUN_FILE_1 Oct 31 14:44
                5085611 pluskal PSUSP normal     it-c05b12   it-c05b12   /lab/solexa_weng/xtal_data/Data_processing/Tomas_Pluskal/20181031_KLR1_autobuild_not_in_place/AutoBuild_run_1_/TEMP0/RUN_FILE_8 Oct 31 14:44
                5085107 pluskal EXIT  normal     it-c01b13   it-c01b07   /lab/solexa_weng/xtal_data/Data_processing/Tomas_Pluskal/20181031_KLR1_autobuild/AutoBuild_run_1_/TEMP0/RUN_FILE_2 Oct 31 13:00
                5085607 pluskal UNKWN normal     it-c05b12   it-c01b07   /lab/solexa_weng/xtal_data/Data_processing/Tomas_Pluskal/20181031_KLR1_autobuild_not_in_place/AutoBuild_run_1_/TEMP0/RUN_FILE_4 Oct 31 14:44
                5085608 pluskal ZOMBI normal     it-c05b12   it-c01b05   /lab/solexa_weng/xtal_data/Data_processing/Tomas_Pluskal/20181031_KLR1_autobuild_not_in_place/AutoBuild_run_1_/TEMP0/RUN_FILE_5 Oct 31 14:44
                5085609 pluskal RUN   normal     it-c05b12   it-c01b14   /lab/solexa_weng/xtal_data/Data_processing/Tomas_Pluskal/20181031_KLR1_autobuild_not_in_place/AutoBuild_run_1_/TEMP0/RUN_FILE_6 Oct 31 14:44
                5085702 pluskal RUN   normal     tak         it-c01b14   ./assemble.nf Oct 31 15:08
                5085606 pluskal DONE  normal     it-c05b12   it-c01b12   /lab/solexa_weng/xtal_data/Data_processing/Tomas_Pluskal/20181031_KLR1_autobuild_not_in_place/AutoBuild_run_1_/TEMP0/RUN_FILE_3 Oct 31 14:44
                5085034 pluskal RUN   normal     tak         it-c01b13   phenix.autobuild autobuild_config.eff Oct 31 12:56
                5085612 pluskal RUN   normal     it-c05b12   it-c01b13   /lab/solexa_weng/xtal_data/Data_processing/Tomas_Pluskal/20181031_KLR1_autobuild_not_in_place/AutoBuild_run_1_/TEMP0/RUN_FILE_9 Oct 31 14:44
                5085703 pluskal RUN   normal     it-c01b14   it-r16u31:it-r16u31:it-r16u31:it-r16u31:it-r16u31:it-r16u31:it-r16u31:it-r16u31:it-r16u31:it-r16u31 nf-trinityInchwormChrysalis Oct 31 15:08
                5085704 longusername RUN   normal     it-c01b15   it-r16u31:it-r16u31:it-r16u31:it-r16u31:it-r16u31:it-r16u31:it-r16u31:it-r16u31:it-r16u31:it-r16u31 nf-trinityInchwormChrysalis Oct 31 16:08
                """.stripIndent().trim()


        when:
        def result = executor.parseQueueStatus(text)
        then:
        result['5085109'] == AbstractGridExecutor.QueueStatus.RUNNING
        result['5085585'] == AbstractGridExecutor.QueueStatus.RUNNING
        result['5085604'] == AbstractGridExecutor.QueueStatus.PENDING
        result['5085611'] == AbstractGridExecutor.QueueStatus.HOLD
        result['5085107'] == AbstractGridExecutor.QueueStatus.ERROR
        result['5085607'] == AbstractGridExecutor.QueueStatus.HOLD
        result['5085608'] == AbstractGridExecutor.QueueStatus.ERROR
        result['5085609'] == AbstractGridExecutor.QueueStatus.RUNNING
        result['5085702'] == AbstractGridExecutor.QueueStatus.RUNNING
        result['5085606'] == AbstractGridExecutor.QueueStatus.DONE
        result['5085034'] == AbstractGridExecutor.QueueStatus.RUNNING
        result['5085612'] == AbstractGridExecutor.QueueStatus.RUNNING
        result['5085703'] == AbstractGridExecutor.QueueStatus.RUNNING
        result['5085704'] == AbstractGridExecutor.QueueStatus.RUNNING
        result.size()==14
    }

    def 'should parse bjobs stats with extra headers' () {
        setup:
        def executor = createExecutor()
        def TEXT = '''
            LSF is processing your request. Please wait ...
            LSF is processing your request. Please wait ...
            JOBID   USER    STAT  QUEUE      FROM_HOST   EXEC_HOST   JOB_NAME   SUBMIT_TIME
            5157393 pluskal RUN   normal     it-c05b07   it-c05b10   *l_cmd_bd) Nov 14 13:00
            5157493 pluskal RUN   normal     it-c05b07   it-c05b10   *l_cmd_cn) Nov 14 13:00
            5157552 pluskal RUN   normal     it-c05b07   it-c05b10   *l_cmd_dl) Nov 14 13:00
            5157610 pluskal RUN   normal     it-c05b07   it-c05b10   *l_cmd_fe) Nov 14 13:00
            5157674 pluskal RUN   normal     it-c05b07   it-c05b10   *l_cmd_gf) Nov 14 13:00
            5157710 pluskal RUN   normal     it-c05b07   it-c05b10   *l_cmd_fv) Nov 14 13:00
            '''.stripIndent().trim()

        when:
        def result = executor.parseQueueStatus(TEXT)
        then:
        result['5157393'] == AbstractGridExecutor.QueueStatus.RUNNING
        result['5157493'] == AbstractGridExecutor.QueueStatus.RUNNING
        result['5157552'] == AbstractGridExecutor.QueueStatus.RUNNING
        result['5157610'] == AbstractGridExecutor.QueueStatus.RUNNING
        result['5157674'] == AbstractGridExecutor.QueueStatus.RUNNING
        result['5157710'] == AbstractGridExecutor.QueueStatus.RUNNING
        result.size() == 6

    }


    def testQueueStatusCommand() {

        setup:
        def executor = createExecutor()

        expect:
        executor.queueStatusCommand(null) == ['bjobs', '-w']
        executor.queueStatusCommand('long') == ['bjobs', '-w', '-q', 'long']

    }



    def testWrapString() {

        given:
        def executor = createExecutor()

        expect:
        executor.wrapHeader('') == ''
        executor.wrapHeader('hello') == 'hello'
        executor.wrapHeader('span[ptile=number]') == '"span[ptile=number]"'
        executor.wrapHeader('hello world') == '"hello world"'

        executor.pipeLauncherScript() == true
        executor.getSubmitCommandLine(Mock(TaskRun), Mock(Path)) == ['bsub']
    }

    def 'should apply per task reserve' () {

        given:
        def config = Spy(ExecutorConfig)
        def executor = createExecutor(config)

        when:
        executor.register()
        then:
        1 * executor.parseLsfConfig() >> [:]
        1 * config.getExecConfigProp(_,'perTaskReserve',_) >> false
        !executor.perTaskReserve

        when:
        executor.register()
        then:
        1 * executor.parseLsfConfig() >> [RESOURCE_RESERVE_PER_TASK:'y']
        1 * config.getExecConfigProp(_,'perTaskReserve',_) >> false
        !executor.perTaskReserve

        when:
        executor.register()
        then:
        1 * executor.parseLsfConfig() >> [:]
        1 * config.getExecConfigProp(_,'perTaskReserve',_) >> true
        executor.perTaskReserve

        when:
        executor.register()
        then:
        1 * executor.parseLsfConfig() >> [RESOURCE_RESERVE_PER_TASK:'y']
        1 * config.getExecConfigProp(_,'perTaskReserve',_) >> { execName,name,defValue -> defValue }
        executor.perTaskReserve
    }

    def 'should apply lsf per job limit' () {
        given:
        def config = Spy(ExecutorConfig)
        def executor = createExecutor(config)

        when:
        executor.register()
        then:
        1 * executor.parseLsfConfig() >> [:]
        1 * config.getExecConfigProp(_,'perJobMemLimit',_) >> false
        !executor.perJobMemLimit

        when:
        executor.register()
        then:
        1 * executor.parseLsfConfig() >> [LSB_JOB_MEMLIMIT:'y']
        1 * config.getExecConfigProp(_,'perJobMemLimit',_) >> false
        !executor.perJobMemLimit

        when:
        executor.register()
        then:
        1 * executor.parseLsfConfig() >> [:]
        1 * config.getExecConfigProp(_,'perJobMemLimit',_) >> true
        executor.perJobMemLimit

        when:
        executor.register()
        then:
        1 * executor.parseLsfConfig() >> [LSB_JOB_MEMLIMIT:'y']
        1 * config.getExecConfigProp(_,'perJobMemLimit',_) >> { execName,name,defValue -> defValue }
        executor.perJobMemLimit
    }


    def 'should parse lsf.config' () {
        given:
        def executor = createExecutor()
        def folder = Files.createTempDirectory('test')
        def file = folder.resolve('lsf.conf')
        file.text = '''
            LSF_FOO=abc
            LSF_BAR="x y z"
            '''.stripIndent()

        when:
        def conf = executor.parseLsfConfig()
        then:
        1 * executor.getEnv0('LSF_ENVDIR') >> null
        conf == [:]

        when:
        conf = executor.parseLsfConfig()
        then:
        1 * executor.getEnv0('LSF_ENVDIR') >> folder.toString()
        conf == [LSF_FOO:'abc', LSF_BAR:'x y z']

        cleanup:
        folder?.deleteDir()
    }

    def 'should parse complex config file' () {
        given:
        def executor = createExecutor()
        def file = new File('src/test/resources/nextflow/executor/lsf.conf')
        assert file.exists(), 'Cannot find LSF config test file'

        when:
        def config = executor.parseLsfConfig()
        then:
        1 * executor.getEnv0('LSF_ENVDIR') >> file.absoluteFile.parent
        then:
        config.LSF_LOGDIR == '/common/foo/bar/log'
        config.LSF_LOG_MASK=='LOG_WARNING'
        config.LSF_LIM_PORT == '7869'
        config.LSF_STRIP_DOMAIN == '.cbio.private:.cbio.delta.org:.delta.org'
        config.LSF_MASTER_LIST == "omega-sched01 omega-sched02"
        config.LSF_API_CONNTIMEOUT == '10'
        config.RESOURCE_RESERVE_PER_TASK == 'Y'
    }

    // Adapted from PbsExecutorTest.groovy
    @Unroll
    def 'should return valid job name given #name'() {
        given:
        def executor = createExecutor()
        def task = Mock(TaskRun)
        task.getName() >> name

        expect:
        executor.getJobNameFor(task) == expected
        executor.getJobNameFor(task).size() <= 4094

        where:
        name               | expected
        'hello'            | 'nf-hello'
        '12 45'            | 'nf-12_45'
        'hello[123]-[xyz]' | 'nf-hello123-xyz'
        'a'.repeat(509)    | 'nf-'.concat("a".repeat(508))
    }

    def 'should get array index name and start' () {
        given:
        def executor = createExecutor()
        expect:
        executor.getArrayIndexName() == 'LSB_JOBINDEX'
        executor.getArrayIndexStart() == 1
    }

    @Unroll
    def 'should get array task id' () {
        given:
        def executor = createExecutor()
        expect:
        executor.getArrayTaskId(JOB_ID, TASK_INDEX) == EXPECTED

        where:
        JOB_ID      | TASK_INDEX    | EXPECTED
        'foo'       | 1             | 'foo[2]'
        'bar'       | 2             | 'bar[3]'
    }

    @Unroll
    def 'should set lsf account' () {
        given:
        // task
        def task = new TaskRun()
        task.workDir = Paths.get('/work/dir')
        task.processor = Mock(TaskProcessor)
        task.processor.getSession() >> Mock(Session)
        task.config = Mock(TaskConfig)  { getClusterOptionsAsList()>>[] }
        and:
        def config = new ExecutorConfig(account: ACCOUNT)
        def executor = createExecutor(config)
        executor.getJobNameFor(_) >> 'foo'
        executor.getName() >> 'lsf'

        when:
        def result = executor.getDirectives(task, [])
        then:
        result == EXPECTED

        where:
        ACCOUNT             | EXPECTED
        null                | ['-o', '/work/dir/.command.log', '-J', 'foo']
        'project-123'       | ['-o', '/work/dir/.command.log', '-J', 'foo', '-G', 'project-123']
    }

}

