/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to you 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.apache.jmeter.control

import org.apache.jmeter.junit.stubs.TestSampler

import spock.lang.Specification

class ThroughputControllerSpec extends Specification {

    def sut = new ThroughputController()

    def setup() {
        sut.addTestElement(new TestSampler("one"))
        sut.addTestElement(new TestSampler("two"))
    }

    def "new TC isDone is true"() {
        given:
            def newTC = new ThroughputController()
        expect:
            newTC.isDone()
    }

    def "2 maxThroughput runs samplers inside the TC only twice"() {
        given:
            sut.setStyle(ThroughputController.BYNUMBER)
            sut.setMaxThroughput(2)

            def expectedNames =
                    ["zero", "one", "two", "three",
                     "zero", "one", "two", "three",
                     "zero", "three",
                     "zero", "three",
                     "zero", "three",]

            def loop = createLoopController(5)
        when:
            def actualNames = expectedNames.collect({
                loop.next().getName()
            })
        then:
            loop.next() == null
            actualNames == expectedNames
            sut.isDone()
            sut.testEnded()
    }

    def "0 maxThroughput does not run any samplers inside the TC"() {
        given:
            sut.setStyle(ThroughputController.BYNUMBER)
            sut.setMaxThroughput(0)

            def loops = 3
            def loop = createLoopController(loops)

            def expectedNames = ["zero", "three"] * loops
        when:
            def actualNames = expectedNames.collect({
                loop.next().getName()
            })
        then:
            loop.next() == null
            actualNames == expectedNames
            sut.testEnded()
    }

    /**
     * <pre>
     *   - innerLoop
     *     - ThroughputController (sut)
     *        - sampler one
     *        - sampler two
     * </pre>
     */
    def "0 maxThroughput does not run any sampler inside the TC and does not cause StackOverFlowError"() {
        given:
            sut.setStyle(ThroughputController.BYNUMBER)
            sut.setMaxThroughput(0)

            LoopController innerLoop = new LoopController()
            innerLoop.setLoops(10000)
            innerLoop.addTestElement(sut)
            innerLoop.addIterationListener(sut)
            innerLoop.initialize()
            innerLoop.setRunningVersion(true)
            sut.testStarted()
            sut.setRunningVersion(true)

        when:
            innerLoop.next() == null
            innerLoop.next() == null
        then:
            sut.testEnded()
    }

    /**
     * <pre>
     *   - innerLoop
     *     - ThroughputController (sut)
     *        - sampler one
     *        - sampler two
     * </pre>
     */
    def "0.0 percentThroughput does not run any sampler inside the TC and does not cause StackOverFlowError"() {
        given:
            sut.setStyle(ThroughputController.BYPERCENT)
            sut.setPercentThroughput("0.0")

            LoopController innerLoop = new LoopController()
            innerLoop.setLoops(10000)
            innerLoop.addTestElement(sut)
            innerLoop.addIterationListener(sut)
            innerLoop.initialize()
            innerLoop.setRunningVersion(true)
            sut.testStarted()
            sut.setRunningVersion(true)

        when:
            innerLoop.next() == null
            innerLoop.next() == null
        then:
            sut.testEnded()
    }

    def "33.33% will run all the samplers inside the TC every 3 iterations"() {
        given:
            sut.setStyle(ThroughputController.BYPERCENT)
            sut.setPercentThroughput(33.33f)

            def loop = createLoopController(9)
            // Expected results established using the DDA algorithm - see:
            // http://www.siggraph.org/education/materials/HyperGraph/scanline/outprims/drawline.htm
            def expectedNames =
                    ["zero", "three", // 0/1 vs. 1/1 -> 0 is closer to 33.33
                     "zero", "one", "two", "three", // 0/2 vs. 1/2 -> 50.0 is closer to 33.33
                     "zero", "three", // 1/3 vs. 2/3 -> 33.33 is closer to 33.33
                     "zero", "three", // 1/4 vs. 2/4 -> 25.0 is closer to 33.33
                     "zero", "one", "two", "three", // 1/5 vs. 2/5 -> 40.0 is closer to 33.33
                     "zero", "three", // 2/6 vs. 3/6 -> 33.33 is closer to 33.33
                     "zero", "three", // 2/7 vs. 3/7 -> 0.2857 is closer to 33.33
                     "zero", "one", "two", "three", // 2/8 vs. 3/8 -> 0.375 is closer to 33.33
                     "zero", "three", // ...
                    ]
        when:
            def actualNames = expectedNames.collect({
                loop.next().getName()
            })
        then:
            loop.next() == null
            actualNames == expectedNames
            sut.testEnded()
    }

    def "0% does not run any samplers inside the TC"() {
        given:
            sut.setStyle(ThroughputController.BYPERCENT)
            sut.setPercentThroughput(0.0f)

            def loops = 3
            def loop = createLoopController(loops)

            def expectedNames = ["zero", "three",] * loops
        when:
            def actualNames = expectedNames.collect({
                loop.next().getName()
            })
        then:
            loop.next() == null
            actualNames == expectedNames
            sut.testEnded()
    }

    def "100% always runs all samplers inside the TC"() {
        given:
            sut.setStyle(ThroughputController.BYPERCENT)
            sut.setPercentThroughput(100.0f)

            def loops = 3
            def loop = createLoopController(loops)

            def expectedNames = ["zero", "one", "two", "three",] * loops
        when:
            def actualNames = expectedNames.collect({
                loop.next().getName()
            })
        then:
            loop.next() == null
            actualNames == expectedNames
            sut.testEnded()
    }

    /**
     * Create a LoopController, executed once, which contains an inner loop that
     * runs {@code innerLoops} times, this inner loop contains a TestSampler ("zero")
     * the {@link ThroughputController} which contains two test samplers
     * ("one" and "two") followed by a final TestSampler ("three"):
     *
     * <pre>
     * - outerLoop
     *   - innerLoop
     *     - sampler zero
     *     - ThroughputController (sut)
     *        - sampler one
     *        - sampler two
     *     - sampler three
     * </pre>
     *
     * @param innerLoops number of times to loop the {@link ThroughputController}
     * @return the{@link LoopController}
     */
    def createLoopController(int innerLoops) {
        LoopController innerLoop = new LoopController()
        innerLoop.setLoops(innerLoops)
        innerLoop.addTestElement(new TestSampler("zero"))
        innerLoop.addTestElement(sut)
        innerLoop.addIterationListener(sut)
        innerLoop.addTestElement(new TestSampler("three"))

        def outerLoop = new LoopController()
        outerLoop.setLoops(1)
        outerLoop.addTestElement(innerLoop)
        sut.testStarted()
        outerLoop.setRunningVersion(true)
        sut.setRunningVersion(true)
        innerLoop.setRunningVersion(true)
        outerLoop.initialize()
        outerLoop
    }
}
