Testing Power Generator Resource Processing in Mindustry
This test suite validates the functionality of power generators in Mindustry that process items and liquids. It ensures proper resource consumption, efficiency calculations, and duration handling for different input types.
Test Coverage Overview
Implementation Analysis
Technical Details
Best Practices Demonstrated
anuken/mindustry
tests/src/test/java/power/ConsumeGeneratorTests.java
package power;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.core.*;
import mindustry.game.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.power.*;
import mindustry.world.blocks.power.ConsumeGenerator.*;
import mindustry.world.consumers.*;
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.DynamicTest.*;
/**
* This class tests generators which can process items, liquids or both.
* All tests are run with a fixed delta of 0.5 so delta considerations can be tested as well.
* Any expected power amount (produced, consumed, buffered) should be affected by fakeDelta but status should not!
*/
public class ConsumeGeneratorTests extends PowerTestFixture{
private ConsumeGeneratorBuild build;
private final float fakeItemDuration = 60f; //ticks
private final float maximumLiquidUsage = 1f;
public void createGenerator(InputType inputType){
Vars.state = new GameState();
Vars.state.rules = new Rules();
ConsumeGenerator generator = new ConsumeGenerator("fakegen" + System.nanoTime()){{
powerProduction = 0.1f;
itemDuration = fakeItemDuration;
buildType = ConsumeGeneratorBuild::new;
liquidCapacity = 100f;
if(inputType != InputType.liquids){
consume(new ConsumeItemFlammable());
}
if(inputType != InputType.items){
consume(new ConsumeLiquidFlammable(maximumLiquidUsage));
}
}};
generator.init();
Tile tile = createFakeTile(0, 0, generator);
build = (ConsumeGeneratorBuild)tile.build;
}
/** Tests the consumption and efficiency when being supplied with liquids. */
@TestFactory
DynamicTest[] generatorWorksProperlyWithLiquidInput(){
// Execute all tests for the case where only liquids are accepted and for the case where liquids and items are accepted (but supply only liquids)
InputType[] inputTypesToBeTested = new InputType[]{
InputType.liquids,
//InputType.any
};
//TODO test with different delta values.
Seq<DynamicTest> tests = new Seq<>();
float[] deltas = {2f, 1f, 0.5f};
for(float d : deltas){
for(InputType inputType : inputTypesToBeTested){
tests.add(dynamicTest("01-delta" + d, () -> simulateLiquidConsumption(d, inputType, Liquids.oil, 0.0f, "No liquids provided")));
tests.add(dynamicTest("02-delta" + d, () -> simulateLiquidConsumption(d, inputType, Liquids.oil, maximumLiquidUsage / 4.0f, "Low oil provided")));
tests.add(dynamicTest("03-delta" + d, () -> simulateLiquidConsumption(d, inputType, Liquids.oil, maximumLiquidUsage * 1.0f, "Sufficient oil provided")));
tests.add(dynamicTest("04-delta" + d, () -> simulateLiquidConsumption(d, inputType, Liquids.oil, maximumLiquidUsage * 2.0f, "Excess oil provided")));
// Note: The generator will decline any other liquid since it's not flammable
}
}
return tests.toArray(DynamicTest.class);
}
void simulateLiquidConsumption(float delta, InputType inputType, Liquid liquid, float availableLiquidAmount, String parameterDescription){
Time.setDeltaProvider(() -> delta);
float expectedConsumptionPerTick = Math.min(maximumLiquidUsage * Time.delta, availableLiquidAmount);
float expectedEfficiency = expectedConsumptionPerTick / (maximumLiquidUsage * Time.delta);
float expectedOutputEfficiency = expectedEfficiency * liquid.flammability;
//it should either consume:
//- the maximum amount used (maximumLiquidUsage) multiplied by speed (delta), or
//- the maximum available amount (availableLiquidAmount), since that's a hard cap
float expectedRemainingLiquidAmount = Math.max(0.0f, availableLiquidAmount - expectedConsumptionPerTick);
createGenerator(inputType);
assertTrue(build.acceptLiquid(null, liquid), inputType + " | " + parameterDescription + ": Liquids which will be declined by the generator don't need to be tested - The code won't be called for those cases.");
build.liquids.add(liquid, availableLiquidAmount);
// Perform an update on the generator once - This should use up any resource up to the maximum liquid usage
build.update();
//reset
Time.setDeltaProvider(() -> 0.5f);
assertEquals(expectedEfficiency, build.efficiency(), inputType + " | " + parameterDescription + ": Base input efficiency mismatch.");
assertEquals(expectedRemainingLiquidAmount, build.liquids.get(liquid), inputType + " | " + parameterDescription + ": Remaining liquid amount mismatch.");
assertEquals(expectedOutputEfficiency, build.productionEfficiency, inputType + " | " + parameterDescription + ": Output production efficiency mismatch.");
}
/** Tests the consumption and efficiency when being supplied with items. */
@TestFactory
DynamicTest[] generatorWorksProperlyWithItemInput(){
// Execute all tests for the case where only items are accepted and for the case where liquids and items are accepted (but supply only items)
InputType[] inputTypesToBeTested = new InputType[]{
InputType.items,
//InputType.any
};
Seq<DynamicTest> tests = new Seq<>();
for(InputType inputType : inputTypesToBeTested){
tests.add(dynamicTest("01", () -> simulateItemConsumption(inputType, Items.coal, 0, "No items provided")));
tests.add(dynamicTest("02", () -> simulateItemConsumption(inputType, Items.coal, 1, "Sufficient coal provided")));
tests.add(dynamicTest("03", () -> simulateItemConsumption(inputType, Items.coal, 10, "Excess coal provided")));
tests.add(dynamicTest("04", () -> simulateItemConsumption(inputType, Items.blastCompound, 1, "Blast compound provided")));
tests.add(dynamicTest("05", () -> simulateItemConsumption(inputType, Items.sporePod, 1, "Biomatter provided")));
tests.add(dynamicTest("06", () -> simulateItemConsumption(inputType, Items.pyratite, 1, "Pyratite provided")));
}
return tests.toArray(DynamicTest.class);
}
void simulateItemConsumption(InputType inputType, Item item, int amount, String parameterDescription){
float expectedEfficiency = amount > 0 ? item.flammability : 0f;
int expectedRemainingItemAmount = Math.max(0, amount - 1);
createGenerator(inputType);
assertTrue(build.acceptItem(null, item), inputType + " | " + parameterDescription + ": Items which will be declined by the generator don't need to be tested - The code won't be called for those cases.");
if(amount > 0){
build.items.add(item, amount);
}
// Perform an update on the generator once - This should use up one or zero items - dependent on if the item is accepted and available or not.
build.update();
assertEquals(expectedRemainingItemAmount, build.items.get(item), inputType + " | " + parameterDescription + ": Remaining item amount mismatch.");
assertEquals(expectedEfficiency, build.productionEfficiency, inputType + " | " + parameterDescription + ": Efficiency mismatch.");
}
/** Makes sure the efficiency stays equal during the item duration. */
@Test
void efficiencyRemainsConstantWithinItemDuration_ItemsOnly(){
testItemDuration(InputType.items);
}
void testItemDuration(InputType inputType){
createGenerator(inputType);
// Burn a single coal and test for the duration
build.items.add(Items.coal, 1);
//first frame update for some setup (consume checking is delayed)
build.update();
float expectedEfficiency = build.productionEfficiency;
float currentDuration = 0.0f;
while((currentDuration += Time.delta) <= fakeItemDuration){
build.update();
assertEquals(expectedEfficiency, build.productionEfficiency, "Duration: " + currentDuration);
}
build.update();
assertEquals(0.0f, build.productionEfficiency, "Duration: " + currentDuration);
}
enum InputType{
items,
liquids
}
}