2018-11-27 08:51:19 +01:00
package power ;
2018-11-22 23:30:49 +01:00
import com.badlogic.gdx.math.MathUtils ;
2018-11-20 20:58:46 +01:00
import io.anuke.mindustry.world.Tile ;
2018-11-28 13:19:52 +01:00
import io.anuke.mindustry.world.blocks.power.PowerGenerator ;
2018-11-20 20:58:46 +01:00
import io.anuke.mindustry.world.blocks.power.PowerGraph ;
2018-11-27 08:51:19 +01:00
import io.anuke.mindustry.world.consumers.ConsumePower ;
2018-11-20 20:58:46 +01:00
import org.junit.jupiter.api.* ;
2018-11-21 00:04:26 +01:00
import static org.junit.jupiter.api.Assertions.assertEquals ;
2018-11-27 08:51:19 +01:00
import static org.junit.jupiter.api.Assertions.assertFalse ;
2018-11-20 20:58:46 +01:00
import static org.junit.jupiter.api.Assertions.assertTrue ;
2018-11-22 23:30:49 +01:00
import static org.junit.jupiter.api.DynamicTest.dynamicTest ;
2018-11-20 20:58:46 +01:00
2018-11-28 11:04:08 +01:00
/ * *
* Tests code related to the power system in general , but not specific blocks .
* All tests are run with a fixed delta of 0 . 5 so delta considerations can be tested as well .
* Additionally , each PowerGraph : : update ( ) call will have its own thread frame , i . e . the method will never be called twice within the same frame .
* Both of these constraints are handled by FakeThreadHandler within PowerTestFixture .
* Any power amount ( produced , consumed , buffered ) should be affected by FakeThreadHandler . fakeDelta but satisfaction should not !
* /
2018-11-22 23:30:49 +01:00
public class PowerTests extends PowerTestFixture {
2018-11-20 20:58:46 +01:00
@BeforeEach
void initTest ( ) {
}
2018-11-22 23:30:49 +01:00
@Nested
class PowerGraphTests {
/ * * Tests the satisfaction of a single consumer after a single update of the power graph which contains a single producer .
*
* Assumption : When the consumer requests zero power , satisfaction does not change . Default is 0 . 0f .
* /
@TestFactory
2018-11-28 23:54:26 +01:00
DynamicTest [ ] directConsumerSatisfactionIsAsExpected ( ) {
2018-11-22 23:30:49 +01:00
return new DynamicTest [ ] {
// Note: Unfortunately, the display names are not yet output through gradle. See https://github.com/gradle/gradle/issues/5975
// That's why we inject the description into the test method for now.
2018-11-28 11:04:08 +01:00
// Additional Note: If you don't see any labels in front of the values supplied as function parameters, use a better IDE like IntelliJ IDEA.
2018-11-28 23:54:26 +01:00
dynamicTest ( " 01 " , ( ) - > simulateDirectConsumption ( 0 . 0f , 1 . 0f , 0 . 0f , " 0.0 produced, 1.0 consumed (no power available) " ) ) ,
dynamicTest ( " 02 " , ( ) - > simulateDirectConsumption ( 0 . 0f , 0 . 0f , 0 . 0f , " 0.0 produced, 0.0 consumed (no power anywhere) " ) ) ,
dynamicTest ( " 03 " , ( ) - > simulateDirectConsumption ( 1 . 0f , 0 . 0f , 0 . 0f , " 1.0 produced, 0.0 consumed (no power requested) " ) ) ,
dynamicTest ( " 04 " , ( ) - > simulateDirectConsumption ( 1 . 0f , 1 . 0f , 1 . 0f , " 1.0 produced, 1.0 consumed (stable consumption) " ) ) ,
dynamicTest ( " 05 " , ( ) - > simulateDirectConsumption ( 0 . 5f , 1 . 0f , 0 . 5f , " 0.5 produced, 1.0 consumed (power shortage) " ) ) ,
dynamicTest ( " 06 " , ( ) - > simulateDirectConsumption ( 1 . 0f , 0 . 5f , 1 . 0f , " 1.0 produced, 0.5 consumed (power excess) " ) ) ,
dynamicTest ( " 07 " , ( ) - > simulateDirectConsumption ( 0 . 09f , 0 . 09f - MathUtils . FLOAT_ROUNDING_ERROR / 10 . 0f , 1 . 0f , " floating point inaccuracy (stable consumption) " ) )
2018-11-22 23:30:49 +01:00
} ;
2018-11-20 20:58:46 +01:00
}
2018-11-28 23:54:26 +01:00
void simulateDirectConsumption ( float producedPower , float requiredPower , float expectedSatisfaction , String parameterDescription ) {
2018-11-22 23:30:49 +01:00
Tile producerTile = createFakeTile ( 0 , 0 , createFakeProducerBlock ( producedPower ) ) ;
2018-11-30 21:08:33 +01:00
producerTile . < PowerGenerator . GeneratorEntity > entity ( ) . productionEfficiency = 0 . 5f ; // Currently, 0.5f = 100%
2018-11-22 23:30:49 +01:00
Tile directConsumerTile = createFakeTile ( 0 , 1 , createFakeDirectConsumer ( requiredPower , 0 . 6f ) ) ;
2018-11-20 20:58:46 +01:00
2018-11-22 23:30:49 +01:00
PowerGraph powerGraph = new PowerGraph ( ) ;
powerGraph . add ( producerTile ) ;
powerGraph . add ( directConsumerTile ) ;
2018-11-20 20:58:46 +01:00
2018-11-28 11:04:08 +01:00
assertEquals ( producedPower * FakeThreadHandler . fakeDelta , powerGraph . getPowerProduced ( ) , MathUtils . FLOAT_ROUNDING_ERROR ) ;
assertEquals ( requiredPower * FakeThreadHandler . fakeDelta , powerGraph . getPowerNeeded ( ) , MathUtils . FLOAT_ROUNDING_ERROR ) ;
2018-11-20 20:58:46 +01:00
2018-11-22 23:30:49 +01:00
// Update and check for the expected power satisfaction of the consumer
powerGraph . update ( ) ;
assertEquals ( expectedSatisfaction , directConsumerTile . entity . power . satisfaction , MathUtils . FLOAT_ROUNDING_ERROR , parameterDescription + " : Satisfaction of direct consumer did not match " ) ;
2018-11-20 20:58:46 +01:00
}
2018-11-22 23:30:49 +01:00
/** Tests the satisfaction of a single buffered consumer after a single update of the power graph which contains a single producer. */
@TestFactory
2018-11-28 23:54:26 +01:00
DynamicTest [ ] bufferedConsumerSatisfactionIsAsExpected ( ) {
2018-11-22 23:30:49 +01:00
return new DynamicTest [ ] {
// Note: powerPerTick may not be 0 in any of the test cases. This would equal a "ticksToFill" of infinite.
2018-11-28 11:04:08 +01:00
// Note: Due to a fixed delta of 0.5, only half of what is defined here will in fact be produced/consumed. Keep this in mind when defining expectedSatisfaction!
2018-11-28 23:54:26 +01:00
dynamicTest ( " 01 " , ( ) - > simulateBufferedConsumption ( 0 . 0f , 0 . 0f , 0 . 1f , 0 . 0f , 0 . 0f , " Empty Buffer, No power anywhere " ) ) ,
dynamicTest ( " 02 " , ( ) - > simulateBufferedConsumption ( 0 . 0f , 1 . 0f , 0 . 1f , 0 . 0f , 0 . 0f , " Empty Buffer, No power provided " ) ) ,
dynamicTest ( " 03 " , ( ) - > simulateBufferedConsumption ( 1 . 0f , 0 . 0f , 0 . 1f , 0 . 0f , 0 . 0f , " Empty Buffer, No power requested " ) ) ,
dynamicTest ( " 04 " , ( ) - > simulateBufferedConsumption ( 1 . 0f , 1 . 0f , 1 . 0f , 0 . 0f , 0 . 5f , " Empty Buffer, Stable Power, One tick to fill " ) ) ,
dynamicTest ( " 05 " , ( ) - > simulateBufferedConsumption ( 2 . 0f , 1 . 0f , 2 . 0f , 0 . 0f , 1 . 0f , " Empty Buffer, Stable Power, One delta to fill " ) ) ,
dynamicTest ( " 06 " , ( ) - > simulateBufferedConsumption ( 1 . 0f , 1 . 0f , 0 . 1f , 0 . 0f , 0 . 05f , " Empty Buffer, Stable Power, multiple ticks to fill " ) ) ,
dynamicTest ( " 07 " , ( ) - > simulateBufferedConsumption ( 1 . 2f , 0 . 5f , 1 . 0f , 0 . 0f , 1 . 0f , " Empty Buffer, Power excess, one delta to fill " ) ) ,
dynamicTest ( " 08 " , ( ) - > simulateBufferedConsumption ( 1 . 0f , 0 . 5f , 0 . 1f , 0 . 0f , 0 . 1f , " Empty Buffer, Power excess, multiple ticks to fill " ) ) ,
dynamicTest ( " 09 " , ( ) - > simulateBufferedConsumption ( 1 . 0f , 1 . 0f , 2 . 0f , 0 . 0f , 0 . 5f , " Empty Buffer, Power shortage, one delta to fill " ) ) ,
dynamicTest ( " 10 " , ( ) - > simulateBufferedConsumption ( 0 . 5f , 1 . 0f , 0 . 1f , 0 . 0f , 0 . 05f , " Empty Buffer, Power shortage, multiple ticks to fill " ) ) ,
dynamicTest ( " 11 " , ( ) - > simulateBufferedConsumption ( 0 . 0f , 1 . 0f , 0 . 1f , 0 . 5f , 0 . 5f , " Unchanged buffer with no power produced " ) ) ,
dynamicTest ( " 12 " , ( ) - > simulateBufferedConsumption ( 1 . 0f , 1 . 0f , 0 . 1f , 1 . 0f , 1 . 0f , " Unchanged buffer when already full " ) ) ,
dynamicTest ( " 13 " , ( ) - > simulateBufferedConsumption ( 0 . 2f , 1 . 0f , 0 . 5f , 0 . 5f , 0 . 6f , " Half buffer, power shortage " ) ) ,
dynamicTest ( " 14 " , ( ) - > simulateBufferedConsumption ( 1 . 0f , 1 . 0f , 0 . 5f , 0 . 9f , 1 . 0f , " Buffer does not get exceeded " ) ) ,
dynamicTest ( " 15 " , ( ) - > simulateBufferedConsumption ( 2 . 0f , 1 . 0f , 1 . 0f , 0 . 5f , 1 . 0f , " Half buffer, filled with excess " ) )
2018-11-22 23:30:49 +01:00
} ;
}
2018-11-28 23:54:26 +01:00
void simulateBufferedConsumption ( float producedPower , float maxBuffer , float powerConsumedPerTick , float initialSatisfaction , float expectedSatisfaction , String parameterDescription ) {
2018-11-22 23:30:49 +01:00
Tile producerTile = createFakeTile ( 0 , 0 , createFakeProducerBlock ( producedPower ) ) ;
2018-11-30 21:08:33 +01:00
producerTile . < PowerGenerator . GeneratorEntity > entity ( ) . productionEfficiency = 0 . 5f ; // Currently, 0.5 = 100%
2018-11-28 11:04:08 +01:00
Tile bufferedConsumerTile = createFakeTile ( 0 , 1 , createFakeBufferedConsumer ( maxBuffer , maxBuffer > 0 . 0f ? maxBuffer / powerConsumedPerTick : 1 . 0f ) ) ;
2018-11-22 23:30:49 +01:00
bufferedConsumerTile . entity . power . satisfaction = initialSatisfaction ;
2018-11-21 23:57:24 +01:00
2018-11-22 23:30:49 +01:00
PowerGraph powerGraph = new PowerGraph ( ) ;
powerGraph . add ( producerTile ) ;
powerGraph . add ( bufferedConsumerTile ) ;
2018-11-21 23:57:24 +01:00
2018-11-28 11:04:08 +01:00
assertEquals ( producedPower * FakeThreadHandler . fakeDelta , powerGraph . getPowerProduced ( ) , MathUtils . FLOAT_ROUNDING_ERROR , parameterDescription + " : Produced power did not match " ) ;
assertEquals ( Math . min ( maxBuffer , powerConsumedPerTick * FakeThreadHandler . fakeDelta ) , powerGraph . getPowerNeeded ( ) , MathUtils . FLOAT_ROUNDING_ERROR , parameterDescription + " : ConsumedPower did not match " ) ;
2018-11-21 23:57:24 +01:00
2018-11-22 23:30:49 +01:00
// Update and check for the expected power satisfaction of the consumer
powerGraph . update ( ) ;
assertEquals ( expectedSatisfaction , bufferedConsumerTile . entity . power . satisfaction , MathUtils . FLOAT_ROUNDING_ERROR , parameterDescription + " : Satisfaction of buffered consumer did not match " ) ;
}
2018-11-26 08:58:16 +01:00
/ * * Tests the satisfaction of a single direct consumer after a single update of the power graph which contains a single producer and a single battery .
* The used battery is created with a maximum capacity of 100 and receives ten power per tick .
* /
@TestFactory
2018-11-28 23:54:26 +01:00
DynamicTest [ ] batteryCapacityIsAsExpected ( ) {
2018-11-26 08:58:16 +01:00
return new DynamicTest [ ] {
2018-11-28 11:04:08 +01:00
// Note: expectedBatteryCapacity is currently adjusted to a delta of 0.5! (FakeThreadHandler sets it to that)
2018-11-28 23:54:26 +01:00
dynamicTest ( " 01 " , ( ) - > simulateDirectConsumptionWithBattery ( 10 . 0f , 0 . 0f , 0 . 0f , 5 . 0f , 0 . 0f , " Empty battery, no consumer " ) ) ,
dynamicTest ( " 02 " , ( ) - > simulateDirectConsumptionWithBattery ( 10 . 0f , 0 . 0f , 94 . 999f , 99 . 999f , 0 . 0f , " Battery almost full after update, no consumer " ) ) ,
dynamicTest ( " 03 " , ( ) - > simulateDirectConsumptionWithBattery ( 10 . 0f , 0 . 0f , 100 . 0f , 100 . 0f , 0 . 0f , " Full battery, no consumer " ) ) ,
dynamicTest ( " 04 " , ( ) - > simulateDirectConsumptionWithBattery ( 0 . 0f , 0 . 0f , 0 . 0f , 0 . 0f , 0 . 0f , " No producer, no consumer, empty battery " ) ) ,
dynamicTest ( " 05 " , ( ) - > simulateDirectConsumptionWithBattery ( 0 . 0f , 0 . 0f , 100 . 0f , 100 . 0f , 0 . 0f , " No producer, no consumer, full battery " ) ) ,
dynamicTest ( " 06 " , ( ) - > simulateDirectConsumptionWithBattery ( 0 . 0f , 10 . 0f , 0 . 0f , 0 . 0f , 0 . 0f , " No producer, empty battery " ) ) ,
dynamicTest ( " 07 " , ( ) - > simulateDirectConsumptionWithBattery ( 0 . 0f , 10 . 0f , 100 . 0f , 95 . 0f , 1 . 0f , " No producer, full battery " ) ) ,
dynamicTest ( " 08 " , ( ) - > simulateDirectConsumptionWithBattery ( 0 . 0f , 10 . 0f , 2 . 5f , 0 . 0f , 0 . 5f , " No producer, low battery " ) ) ,
dynamicTest ( " 09 " , ( ) - > simulateDirectConsumptionWithBattery ( 5 . 0f , 10 . 0f , 5 . 0f , 0 . 0f , 1 . 0f , " Producer + Battery = Consumed " ) ) ,
2018-11-26 08:58:16 +01:00
} ;
}
2018-11-28 23:54:26 +01:00
void simulateDirectConsumptionWithBattery ( float producedPower , float requestedPower , float initialBatteryCapacity , float expectedBatteryCapacity , float expectedSatisfaction , String parameterDescription ) {
2018-11-26 09:24:08 +01:00
PowerGraph powerGraph = new PowerGraph ( ) ;
if ( producedPower > 0 . 0f ) {
Tile producerTile = createFakeTile ( 0 , 0 , createFakeProducerBlock ( producedPower ) ) ;
2018-11-30 21:08:33 +01:00
producerTile . < PowerGenerator . GeneratorEntity > entity ( ) . productionEfficiency = 0 . 5f ; // Currently, 0.5f = 100%
2018-11-26 09:24:08 +01:00
powerGraph . add ( producerTile ) ;
}
Tile directConsumerTile = null ;
if ( requestedPower > 0 . 0f ) {
directConsumerTile = createFakeTile ( 0 , 1 , createFakeDirectConsumer ( requestedPower , 0 . 6f ) ) ;
powerGraph . add ( directConsumerTile ) ;
}
2018-11-26 08:58:16 +01:00
float maxCapacity = 100f ;
Tile batteryTile = createFakeTile ( 0 , 2 , createFakeBattery ( maxCapacity , 10 ) ) ;
batteryTile . entity . power . satisfaction = initialBatteryCapacity / maxCapacity ;
powerGraph . add ( batteryTile ) ;
powerGraph . update ( ) ;
2018-11-28 11:04:08 +01:00
assertEquals ( expectedBatteryCapacity / maxCapacity , batteryTile . entity . power . satisfaction , MathUtils . FLOAT_ROUNDING_ERROR , parameterDescription + " : Expected battery satisfaction did not match " ) ;
2018-11-26 09:24:08 +01:00
if ( directConsumerTile ! = null ) {
assertEquals ( expectedSatisfaction , directConsumerTile . entity . power . satisfaction , MathUtils . FLOAT_ROUNDING_ERROR , parameterDescription + " : Satisfaction of direct consumer did not match " ) ;
}
2018-11-26 08:58:16 +01:00
}
2018-11-27 08:51:19 +01:00
/** Makes sure a direct consumer stops working after power production is set to zero. */
@Test
2018-11-28 23:54:26 +01:00
void directConsumptionStopsWithNoPower ( ) {
2018-11-27 08:51:19 +01:00
Tile producerTile = createFakeTile ( 0 , 0 , createFakeProducerBlock ( 10 . 0f ) ) ;
2018-11-28 13:19:52 +01:00
producerTile . < PowerGenerator . GeneratorEntity > entity ( ) . productionEfficiency = 1 . 0f ;
2018-11-27 08:51:19 +01:00
Tile consumerTile = createFakeTile ( 0 , 1 , createFakeDirectConsumer ( 5 . 0f , 0 . 6f ) ) ;
PowerGraph powerGraph = new PowerGraph ( ) ;
powerGraph . add ( producerTile ) ;
powerGraph . add ( consumerTile ) ;
powerGraph . update ( ) ;
assertEquals ( 1 . 0f , consumerTile . entity . power . satisfaction , MathUtils . FLOAT_ROUNDING_ERROR ) ;
powerGraph . remove ( producerTile ) ;
powerGraph . add ( consumerTile ) ;
powerGraph . update ( ) ;
assertEquals ( 0 . 0f , consumerTile . entity . power . satisfaction , MathUtils . FLOAT_ROUNDING_ERROR ) ;
if ( consumerTile . block ( ) . consumes . has ( ConsumePower . class ) ) {
ConsumePower consumePower = consumerTile . block ( ) . consumes . get ( ConsumePower . class ) ;
assertFalse ( consumePower . valid ( consumerTile . block ( ) , consumerTile . entity ( ) ) ) ;
}
}
2018-11-21 23:57:24 +01:00
}
2018-11-20 20:58:46 +01:00
}