import arc.*; import arc.backend.headless.*; import arc.func.*; import arc.math.*; import arc.math.geom.*; import arc.struct.*; import arc.util.*; import mindustry.*; import mindustry.content.*; import mindustry.core.*; import mindustry.core.GameState.*; import mindustry.ctype.*; import mindustry.entities.units.*; import mindustry.game.*; import mindustry.gen.*; import mindustry.io.*; import mindustry.maps.*; import mindustry.net.Net; import mindustry.type.*; import mindustry.world.*; import org.junit.jupiter.api.*; import static mindustry.Vars.*; import static org.junit.jupiter.api.Assertions.*; public class ApplicationTests{ static Map testMap; static boolean initialized; @BeforeAll static void launchApplication(){ //only gets called once if(initialized) return; initialized = true; try{ boolean[] begins = {false}; Throwable[] exceptionThrown = {null}; Log.setUseColors(false); ApplicationCore core = new ApplicationCore(){ @Override public void setup(){ headless = true; net = new Net(null); tree = new FileTree(); Vars.init(); world = new World(){ @Override public float getDarkness(int x, int y){ //for world borders return 0; } }; content.createBaseContent(); add(logic = new Logic()); add(netServer = new NetServer()); content.init(); } @Override public void init(){ super.init(); begins[0] = true; testMap = maps.loadInternalMap("groundZero"); Thread.currentThread().interrupt(); } }; new HeadlessApplication(core, null, throwable -> exceptionThrown[0] = throwable); while(!begins[0]){ if(exceptionThrown[0] != null){ fail(exceptionThrown[0]); } Thread.sleep(10); } }catch(Throwable r){ fail(r); } } @BeforeEach void resetWorld(){ Time.setDeltaProvider(() -> 1f); logic.reset(); state.set(State.menu); } @Test void initialization(){ assertNotNull(logic); assertNotNull(world); assertTrue(content.getContentMap().length > 0); } @Test void playMap(){ world.loadMap(testMap); } @Test void spawnWaves(){ world.loadMap(testMap); assertTrue(spawner.countSpawns() > 0, "No spawns present."); logic.runWave(); //force trigger delayed spawns Time.setDeltaProvider(() -> 1000f); Time.update(); Time.update(); Time.setDeltaProvider(() -> 1f); Groups.unit.update(); assertFalse(Groups.unit.isEmpty(), "No enemies spawned."); } @Test void createMap(){ Tiles tiles = world.resize(8, 8); world.beginMapLoad(); tiles.fill(); world.endMapLoad(); } @Test void multiblock(){ createMap(); int bx = 4; int by = 4; world.tile(bx, by).setBlock(Blocks.coreShard, Team.sharded, 0); assertEquals(world.tile(bx, by).team(), Team.sharded); for(int x = bx - 1; x <= bx + 1; x++){ for(int y = by - 1; y <= by + 1; y++){ assertEquals(world.tile(x, y).block(), Blocks.coreShard); assertEquals(world.tile(x, y).entity, world.tile(bx, by).entity); } } } @Test void blockInventories(){ multiblock(); Tile tile = world.tile(4, 4); tile.entity.items().add(Items.coal, 5); tile.entity.items().add(Items.titanium, 50); assertEquals(tile.entity.items().total(), 55); tile.entity.items().remove(Items.phasefabric, 10); tile.entity.items().remove(Items.titanium, 10); assertEquals(tile.entity.items().total(), 45); } @Test void timers(){ boolean[] ran = {false}; Time.run(1.9999f, () -> ran[0] = true); Time.update(); assertFalse(ran[0]); Time.update(); assertTrue(ran[0]); } @Test void manyTimers(){ int runs = 100000; int[] total = {0}; for(int i = 0; i < runs; i++){ Time.run(0.999f, () -> total[0]++); } assertEquals(0, total[0]); Time.update(); assertEquals(runs, total[0]); } @Test void longTimers(){ Time.setDeltaProvider(() -> Float.MAX_VALUE); Time.update(); int steps = 100; float delay = 100000f; Time.setDeltaProvider(() -> delay / steps + 0.01f); int runs = 100000; int[] total = {0}; for(int i = 0; i < runs; i++){ Time.run(delay, () -> total[0]++); } assertEquals(0, total[0]); for(int i = 0; i < steps; i++){ Time.update(); } assertEquals(runs, total[0]); } @Test void save(){ world.loadMap(testMap); assertTrue(state.teams.playerCores().size > 0); SaveIO.save(saveDirectory.child("0.msav")); } @Test void load(){ world.loadMap(testMap); Map map = state.map; SaveIO.save(saveDirectory.child("0.msav")); resetWorld(); SaveIO.load(saveDirectory.child("0.msav")); assertEquals(world.width(), map.width); assertEquals(world.height(), map.height); assertTrue(state.teams.playerCores().size > 0); } void updateBlocks(int times){ for(Tile tile : world.tiles){ if(tile.entity != null && tile.isCenter()){ tile.entity.updateProximity(); } } for(int i = 0; i < times; i++){ Time.update(); for(Tile tile : world.tiles){ if(tile.entity != null && tile.isCenter()){ tile.entity.update(); } } } } @Test void liquidOutput(){ world.loadMap(testMap); state.set(State.playing); world.tile(0, 0).setBlock(Blocks.liquidSource); world.tile(0, 0).entity.configureAny(Liquids.water); world.tile(2, 1).setBlock(Blocks.liquidTank); updateBlocks(10); assertTrue(world.tile(2, 1).entity.liquids().currentAmount() >= 1); assertTrue(world.tile(2, 1).entity.liquids().current() == Liquids.water); } @Test void liquidJunctionOutput(){ world.loadMap(testMap); state.set(State.playing); Tile source = world.rawTile(0, 0), tank = world.rawTile(1, 4), junction = world.rawTile(0, 1), conduit = world.rawTile(0, 2); source.setBlock(Blocks.liquidSource); source.entity.configureAny(Liquids.water); junction.setBlock(Blocks.liquidJunction); conduit.setBlock(Blocks.conduit, Team.derelict, 1); tank.setBlock(Blocks.liquidTank); updateBlocks(10); assertTrue(tank.entity.liquids().currentAmount() >= 1, "Liquid not moved through junction"); assertTrue(tank.entity.liquids().current() == Liquids.water, "Tank has no water"); } @Test void blockOverlapRemoved(){ world.loadMap(testMap); state.set(State.playing); //edge block world.tile(1, 1).setBlock(Blocks.coreShard); assertEquals(Blocks.coreShard, world.tile(0, 0).block()); //this should overwrite the block world.tile(2, 2).setBlock(Blocks.coreShard); assertEquals(Blocks.air, world.tile(0, 0).block()); } @Test void conveyorCrash(){ world.loadMap(testMap); state.set(State.playing); world.tile(0, 0).setBlock(Blocks.conveyor); world.tile(0, 0).rotation(0); world.tile(0, 0).entity.acceptStack(Items.copper, 1000, null); } @Test void indexingBasic(){ resetWorld(); SaveIO.load(Core.files.internal("77.msav")); //test basic method. Rand r = new Rand(0); Tilec[] res = {null}; Cons assigner = t -> res[0] = t; int iterations = 100; r.setSeed(0); //warmup. for(int i = 0; i < iterations; i++){ int x = r.random(0, world.width()), y = r.random(0, world.height()); float range = r.random(tilesize * 30); indexer.eachBlock(Team.sharded, x * tilesize, y * tilesize, range, t -> true, assigner); } //TODO impl /* r.setSeed(0); for(int i = 0; i < iterations; i++){ int x = r.random(0, world.width()), y = r.random(0, world.height()); float range = r.random(tilesize * 30); indexer.eachBlock2(Team.sharded, x * tilesize, y * tilesize, range, t -> true, assigner); }*/ //benchmark. r.setSeed(0); Time.mark(); for(int i = 0; i < iterations; i++){ int x = r.random(0, world.width()), y = r.random(0, world.height()); float range = r.random(tilesize * 30); indexer.eachBlock(Team.sharded, x * tilesize, y * tilesize, range, t -> true, assigner); } Log.info("Time for basic indexing: @", Time.elapsed()); r.setSeed(0); /* Time.mark(); for(int i = 0; i < iterations; i++){ int x = r.random(0, world.width()), y = r.random(0, world.height()); float range = r.random(tilesize * 30); indexer.eachBlock2(Team.sharded, x * tilesize, y * tilesize, range, t -> true, assigner); } Log.info("Time for quad: {0}", Time.elapsed()); */ } @Test void conveyorBench(){ int[] itemsa = {0}; world.loadMap(testMap); state.set(State.playing); int length = 128; world.tile(0, 0).setBlock(Blocks.itemSource); world.tile(0, 0).entity.configureAny(Items.copper); Array entities = Array.with(world.tile(0, 0).entity); for(int i = 0; i < length; i++){ world.tile(i + 1, 0).setBlock(Blocks.conveyor); world.tile(i + 1, 0).rotation(0); entities.add(world.tile(i + 1, 0).entity); } world.tile(length + 1, 0).setBlock(new Block("___"){{ hasItems = true; destructible = true; entityType = () -> new TileEntity(){ @Override public void handleItem(Tilec source, Item item){ itemsa[0] ++; } @Override public boolean acceptItem(Tilec source, Item item){ return true; } }; }}); entities.each(Tilec::updateProximity); //warmup for(int i = 0; i < 100000; i++){ entities.each(Tilec::update); } Time.mark(); for(int i = 0; i < 200000; i++){ entities.each(Tilec::update); } Log.info(Time.elapsed() + "ms to process " + itemsa[0] + " items"); assertNotEquals(0, itemsa[0]); } @Test void load77Save(){ resetWorld(); SaveIO.load(Core.files.internal("77.msav")); //just tests if the map was loaded properly and didn't crash, no validity checks currently assertEquals(276, world.width()); assertEquals(10, world.height()); } @Test void load85Save(){ resetWorld(); SaveIO.load(Core.files.internal("85.msav")); assertEquals(250, world.width()); assertEquals(300, world.height()); } @Test void arrayIterators(){ Array arr = Array.with("a", "b" , "c", "d", "e", "f"); Array results = new Array<>(); for(String s : arr); for(String s : results); Array.iteratorsAllocated = 0; //simulate non-enhanced for loops, which should be correct for(int i = 0; i < arr.size; i++){ for(int j = 0; j < arr.size; j++){ results.add(arr.get(i) + arr.get(j)); } } int index = 0; //test nested for loops for(String s : arr){ for(String s2 : arr){ assertEquals(results.get(index++), s + s2); } } assertEquals(results.size, index); assertEquals(0, Array.iteratorsAllocated, "No new iterators must have been allocated."); } @Test void inventoryDeposit(){ depositTest(Blocks.surgeSmelter, Items.copper); depositTest(Blocks.vault, Items.copper); depositTest(Blocks.thoriumReactor, Items.thorium); } @Test void edges(){ Point2[] edges = Edges.getEdges(1); assertEquals(edges[0], new Point2(1, 0)); assertEquals(edges[1], new Point2(0, 1)); assertEquals(edges[2], new Point2(-1, 0)); assertEquals(edges[3], new Point2(0, -1)); Point2[] edges2 = Edges.getEdges(2); assertEquals(8, edges2.length); } @Test void buildingOverlap(){ initBuilding(); Builderc d1 = (Builderc)UnitTypes.phantom.create(Team.sharded); Builderc d2 = (Builderc)UnitTypes.phantom.create(Team.sharded); //infinite build range state.rules.editor = true; state.rules.infiniteResources = true; state.rules.buildSpeedMultiplier = 999999f; d1.set(0f, 0f); d2.set(20f, 20f); d1.addBuild(new BuildRequest(0, 0, 0, Blocks.copperWallLarge)); d2.addBuild(new BuildRequest(1, 1, 0, Blocks.copperWallLarge)); d1.update(); d2.update(); assertEquals(Blocks.copperWallLarge, world.tile(0, 0).block()); assertEquals(Blocks.air, world.tile(2, 2).block()); assertEquals(Blocks.copperWallLarge, world.tile(1, 1).block()); assertEquals(world.tile(1, 1).entity, world.tile(0, 0).entity); } @Test void buildingDestruction(){ initBuilding(); Builderc d1 = (Builderc)UnitTypes.phantom.create(Team.sharded); Builderc d2 = (Builderc)UnitTypes.phantom.create(Team.sharded); d1.set(10f, 20f); d2.set(10f, 20f); d1.addBuild(new BuildRequest(0, 0, 0, Blocks.copperWallLarge)); d2.addBuild(new BuildRequest(1, 1)); Time.setDeltaProvider(() -> 3f); d1.update(); Time.setDeltaProvider(() -> 1f); d2.update(); assertEquals(content.getByName(ContentType.block, "build2"), world.tile(0, 0).block()); Time.setDeltaProvider(() -> 9999f); d1.update(); assertEquals(Blocks.copperWallLarge, world.tile(0, 0).block()); assertEquals(Blocks.copperWallLarge, world.tile(1, 1).block()); d2.clearBuilding(); d2.addBuild(new BuildRequest(1, 1)); d2.update(); assertEquals(Blocks.air, world.tile(0, 0).block()); assertEquals(Blocks.air, world.tile(2, 2).block()); assertEquals(Blocks.air, world.tile(1, 1).block()); } @Test void allBlockTest(){ Tiles tiles = world.resize(256*2 + 20, 10); world.beginMapLoad(); for(int x = 0; x < tiles.width; x++){ for(int y = 0; y < tiles.height; y++){ tiles.set(x, y, new Tile(x, y, Blocks.stone, Blocks.air, Blocks.air)); } } int i = 0; for(int x = 5; x < tiles.width && i < content.blocks().size; ){ Block block = content.block(i++); if(block.canBeBuilt()){ x += block.size; tiles.get(x, 5).setBlock(block); x += block.size; } } world.endMapLoad(); for(int x = 0; x < tiles.width; x++){ for(int y = 0; y < tiles.height; y++){ Tile tile = world.rawTile(x, y); if(tile.entity != null){ try{ tile.entity.update(); }catch(Throwable t){ fail("Failed to update block '" + tile.block() + "'.", t); } assertEquals(tile.block(), tile.entity.block()); assertEquals(tile.block().health, tile.entity.health()); } } } } void initBuilding(){ createMap(); Tile core = world.tile(5, 5); core.setBlock(Blocks.coreShard, Team.sharded, 0); for(Item item : content.items()){ core.entity.items().set(item, 3000); } assertEquals(core.entity, state.teams.get(Team.sharded).core()); } void depositTest(Block block, Item item){ Unitc unit = UnitTypes.spirit.create(Team.derelict); Tile tile = new Tile(0, 0, Blocks.air, Blocks.air, block); int capacity = tile.block().itemCapacity; assertNotNull(tile.entity, "Tile should have an entity, but does not: " + tile); int deposited = tile.entity.acceptStack(item, capacity - 1, unit); assertEquals(capacity - 1, deposited); tile.entity.handleStack(item, capacity - 1, unit); assertEquals(tile.entity.items().get(item), capacity - 1); int overflow = tile.entity.acceptStack(item, 10, unit); assertEquals(1, overflow); tile.entity.handleStack(item, 1, unit); assertEquals(capacity, tile.entity.items().get(item)); } }