mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-01-07 07:50:27 +00:00
Add a couple of tests for pocket computers
- Ensure they're correctly synced to the client. This definitely isn't comprehensive, but doing anything further probably involves multiple players, which is tricky. - Quick rendering test for in-hand computers.
This commit is contained in:
parent
0fc78acd49
commit
3a96aea894
@ -40,12 +40,6 @@ abstract class ClientJavaExec : JavaExec() {
|
|||||||
@get:Input
|
@get:Input
|
||||||
val useFramebuffer get() = !clientDebug && !project.hasProperty("clientNoFramebuffer")
|
val useFramebuffer get() = !clientDebug && !project.hasProperty("clientNoFramebuffer")
|
||||||
|
|
||||||
/**
|
|
||||||
* The folder screenshots are written to.
|
|
||||||
*/
|
|
||||||
@get:OutputDirectory
|
|
||||||
val screenshots = project.layout.buildDirectory.dir("testScreenshots")
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The path test results are written to.
|
* The path test results are written to.
|
||||||
*/
|
*/
|
||||||
@ -109,11 +103,6 @@ abstract class ClientJavaExec : JavaExec() {
|
|||||||
} else {
|
} else {
|
||||||
super.exec()
|
super.exec()
|
||||||
}
|
}
|
||||||
|
|
||||||
fsOperations.copy {
|
|
||||||
from(workingDir.resolve("screenshots"))
|
|
||||||
into(screenshots)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@get:Inject
|
@get:Inject
|
||||||
|
@ -49,9 +49,9 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
|
|||||||
private static final String NBT_UPGRADE = "Upgrade";
|
private static final String NBT_UPGRADE = "Upgrade";
|
||||||
private static final String NBT_UPGRADE_INFO = "UpgradeInfo";
|
private static final String NBT_UPGRADE_INFO = "UpgradeInfo";
|
||||||
public static final String NBT_LIGHT = "Light";
|
public static final String NBT_LIGHT = "Light";
|
||||||
private static final String NBT_ON = "On";
|
public static final String NBT_ON = "On";
|
||||||
|
|
||||||
private static final String NBT_INSTANCE = "Instanceid";
|
private static final String NBT_INSTANCE = "InstanceId";
|
||||||
private static final String NBT_SESSION = "SessionId";
|
private static final String NBT_SESSION = "SessionId";
|
||||||
|
|
||||||
private final ComputerFamily family;
|
private final ComputerFamily family;
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||||
|
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
|
||||||
|
* Send enquiries to dratcliffe@gmail.com
|
||||||
|
*/
|
||||||
|
package dan200.computercraft.gametest.core;
|
||||||
|
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extensions to {@link Minecraft}, injected via mixin.
|
||||||
|
*/
|
||||||
|
public interface MinecraftExtensions {
|
||||||
|
boolean computercraft$isRenderingStable();
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||||
|
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
|
||||||
|
* Send enquiries to dratcliffe@gmail.com
|
||||||
|
*/
|
||||||
|
package dan200.computercraft.mixin.gametest.client;
|
||||||
|
|
||||||
|
import dan200.computercraft.gametest.core.MinecraftExtensions;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.multiplayer.ClientLevel;
|
||||||
|
import net.minecraft.client.player.LocalPlayer;
|
||||||
|
import net.minecraft.client.renderer.LevelRenderer;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
@Mixin(Minecraft.class)
|
||||||
|
class MinecraftMixin implements MinecraftExtensions {
|
||||||
|
@Final
|
||||||
|
@Shadow
|
||||||
|
public LevelRenderer levelRenderer;
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
@Nullable
|
||||||
|
public ClientLevel level;
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
@Nullable
|
||||||
|
public LocalPlayer player;
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private final AtomicBoolean isStable = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
@Inject(method = "runTick", at = @At("TAIL"))
|
||||||
|
private void updateStable(boolean render, CallbackInfo ci) {
|
||||||
|
isStable.set(
|
||||||
|
level != null && player != null &&
|
||||||
|
levelRenderer.isChunkCompiled(player.blockPosition()) && levelRenderer.countRenderedChunks() > 10 &&
|
||||||
|
levelRenderer.hasRenderedAllChunks()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean computercraft$isRenderingStable() {
|
||||||
|
return isStable.get();
|
||||||
|
}
|
||||||
|
}
|
@ -44,7 +44,7 @@ class Inventory_Test {
|
|||||||
/**
|
/**
|
||||||
* Ensures inventory methods check an item is valid before moving it.
|
* Ensures inventory methods check an item is valid before moving it.
|
||||||
*
|
*
|
||||||
* @see <https://github.com/cc-tweaked/cc-restitched/issues/121>
|
* @see <https://github.com/cc-tweaked/cc-restitched/issues/122>
|
||||||
*/
|
*/
|
||||||
@GameTest
|
@GameTest
|
||||||
fun Fails_on_full(helper: GameTestHelper) = helper.sequence {
|
fun Fails_on_full(helper: GameTestHelper) = helper.sequence {
|
||||||
|
@ -0,0 +1,100 @@
|
|||||||
|
package dan200.computercraft.gametest
|
||||||
|
|
||||||
|
import dan200.computercraft.api.lua.ObjectArguments
|
||||||
|
import dan200.computercraft.client.pocket.ClientPocketComputers
|
||||||
|
import dan200.computercraft.core.apis.TermAPI
|
||||||
|
import dan200.computercraft.gametest.api.*
|
||||||
|
import dan200.computercraft.mixin.gametest.GameTestHelperAccessor
|
||||||
|
import dan200.computercraft.shared.ModRegistry
|
||||||
|
import dan200.computercraft.shared.computer.core.ComputerState
|
||||||
|
import dan200.computercraft.shared.pocket.items.PocketComputerItem
|
||||||
|
import dan200.computercraft.test.core.computer.getApi
|
||||||
|
import net.minecraft.core.BlockPos
|
||||||
|
import net.minecraft.gametest.framework.GameTestHelper
|
||||||
|
import net.minecraft.gametest.framework.GameTestSequence
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
class Pocket_Computer_Test {
|
||||||
|
/**
|
||||||
|
* Checks pocket computer state is synced to the holding player.
|
||||||
|
*/
|
||||||
|
@ClientGameTest(template = Structures.DEFAULT)
|
||||||
|
fun Sync_state(context: GameTestHelper) = context.sequence {
|
||||||
|
// We use a unique label for each test run as computers from previous runs may not have been disposed yet.
|
||||||
|
val unique = java.lang.Long.toHexString(Random.nextLong())
|
||||||
|
|
||||||
|
// Give the player a pocket computer.
|
||||||
|
thenExecute {
|
||||||
|
context.positionAt(BlockPos(2, 2, 2))
|
||||||
|
context.givePocketComputer(unique)
|
||||||
|
}
|
||||||
|
// Write some text to the computer.
|
||||||
|
thenOnComputer(unique) { getApi<TermAPI>().write(ObjectArguments("Hello, world!")) }
|
||||||
|
// And ensure its synced to the client.
|
||||||
|
thenIdle(4)
|
||||||
|
thenOnClient {
|
||||||
|
val pocketComputer = ClientPocketComputers.get(minecraft.player!!.mainHandItem)
|
||||||
|
assertEquals(ComputerState.ON, pocketComputer.state)
|
||||||
|
|
||||||
|
val term = pocketComputer.terminal
|
||||||
|
assertEquals("Hello, world!", term.getLine(0).toString().trim(), "Terminal contents is synced")
|
||||||
|
}
|
||||||
|
// Update the terminal contents again.
|
||||||
|
thenOnComputer(unique) {
|
||||||
|
val term = getApi<TermAPI>()
|
||||||
|
term.setCursorPos(1, 1)
|
||||||
|
term.setCursorBlink(true)
|
||||||
|
term.write(ObjectArguments("Updated text :)"))
|
||||||
|
}
|
||||||
|
// And ensure the new computer state and terminal are sent.
|
||||||
|
thenIdle(4)
|
||||||
|
thenOnClient {
|
||||||
|
val pocketComputer = ClientPocketComputers.get(minecraft.player!!.mainHandItem)
|
||||||
|
assertEquals(ComputerState.BLINKING, pocketComputer.state)
|
||||||
|
|
||||||
|
val term = pocketComputer.terminal
|
||||||
|
assertEquals("Updated text :)", term.getLine(0).toString().trim(), "Terminal contents is synced")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks pocket computers are rendered when being held like a map.
|
||||||
|
*/
|
||||||
|
@ClientGameTest(template = Structures.DEFAULT)
|
||||||
|
fun Renders_map_view(context: GameTestHelper) = context.sequence {
|
||||||
|
// We use a unique label for each test run as computers from previous runs may not have been disposed yet.
|
||||||
|
val unique = java.lang.Long.toHexString(Random.nextLong())
|
||||||
|
|
||||||
|
// Give the player a pocket computer.
|
||||||
|
thenExecute {
|
||||||
|
context.positionAt(BlockPos(2, 2, 2), xRot = 90.0f)
|
||||||
|
context.givePocketComputer(unique)
|
||||||
|
}
|
||||||
|
thenOnComputer(unique) {
|
||||||
|
val terminal = getApi<TermAPI>().terminal
|
||||||
|
terminal.write("Hello, world!")
|
||||||
|
terminal.setCursorPos(1, 2)
|
||||||
|
terminal.textColour = 2
|
||||||
|
terminal.backgroundColour = 3
|
||||||
|
terminal.write("Some coloured text")
|
||||||
|
}
|
||||||
|
thenIdle(4)
|
||||||
|
thenScreenshot(showGui = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Give the current player a pocket computer, suitable to be controlled by [GameTestSequence.thenOnComputer].
|
||||||
|
*/
|
||||||
|
private fun GameTestHelper.givePocketComputer(name: String? = null) {
|
||||||
|
val player = level.randomPlayer!!
|
||||||
|
player.inventory.clearContent()
|
||||||
|
|
||||||
|
val testName = (this as GameTestHelperAccessor).testInfo.testName
|
||||||
|
val label = testName + (if (name == null) "" else ".$name")
|
||||||
|
|
||||||
|
val item = ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get().create(1, label, -1, null)
|
||||||
|
item.getOrCreateTag().putBoolean(PocketComputerItem.NBT_ON, true)
|
||||||
|
player.inventory.setItem(0, item)
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package dan200.computercraft.gametest.api
|
package dan200.computercraft.gametest.api
|
||||||
|
|
||||||
|
import dan200.computercraft.gametest.core.MinecraftExtensions
|
||||||
import dan200.computercraft.mixin.gametest.GameTestSequenceAccessor
|
import dan200.computercraft.mixin.gametest.GameTestSequenceAccessor
|
||||||
import dan200.computercraft.shared.platform.Registries
|
import dan200.computercraft.shared.platform.Registries
|
||||||
import net.minecraft.client.Minecraft
|
import net.minecraft.client.Minecraft
|
||||||
@ -20,9 +21,7 @@ import java.util.concurrent.atomic.AtomicBoolean
|
|||||||
/**
|
/**
|
||||||
* Attempt to guess whether all chunks have been rendered.
|
* Attempt to guess whether all chunks have been rendered.
|
||||||
*/
|
*/
|
||||||
fun Minecraft.isRenderingStable(): Boolean = level != null && player != null &&
|
fun Minecraft.isRenderingStable(): Boolean = (this as MinecraftExtensions).`computercraft$isRenderingStable`()
|
||||||
levelRenderer.isChunkCompiled(player!!.blockPosition()) && levelRenderer.countRenderedChunks() > 10 &&
|
|
||||||
levelRenderer.hasRenderedAllChunks()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run a task on the client.
|
* Run a task on the client.
|
||||||
@ -44,7 +43,7 @@ fun GameTestSequence.thenOnClient(task: ClientTestHelper.() -> Unit): GameTestSe
|
|||||||
/**
|
/**
|
||||||
* Take a screenshot of the current game state.
|
* Take a screenshot of the current game state.
|
||||||
*/
|
*/
|
||||||
fun GameTestSequence.thenScreenshot(name: String? = null): GameTestSequence {
|
fun GameTestSequence.thenScreenshot(name: String? = null, showGui: Boolean = false): GameTestSequence {
|
||||||
val suffix = if (name == null) "" else "-$name"
|
val suffix = if (name == null) "" else "-$name"
|
||||||
val test = (this as GameTestSequenceAccessor).parent
|
val test = (this as GameTestSequenceAccessor).parent
|
||||||
val fullName = "${test.testName}$suffix"
|
val fullName = "${test.testName}$suffix"
|
||||||
@ -63,7 +62,7 @@ fun GameTestSequence.thenScreenshot(name: String? = null): GameTestSequence {
|
|||||||
|
|
||||||
// Now disable the GUI, take a screenshot and reenable it. Sleep a little afterwards to ensure the render thread
|
// Now disable the GUI, take a screenshot and reenable it. Sleep a little afterwards to ensure the render thread
|
||||||
// has caught up.
|
// has caught up.
|
||||||
thenOnClient { minecraft.options.hideGui = true }
|
thenOnClient { minecraft.options.hideGui = !showGui }
|
||||||
thenIdle(2)
|
thenIdle(2)
|
||||||
|
|
||||||
// Take a screenshot and wait for it to have finished.
|
// Take a screenshot and wait for it to have finished.
|
||||||
@ -96,12 +95,12 @@ fun GameTestHelper.positionAtArmorStand() {
|
|||||||
/**
|
/**
|
||||||
* Position the player at a given coordinate.
|
* Position the player at a given coordinate.
|
||||||
*/
|
*/
|
||||||
fun GameTestHelper.positionAt(pos: BlockPos) {
|
fun GameTestHelper.positionAt(pos: BlockPos, yRot: Float = 0.0f, xRot: Float = 0.0f) {
|
||||||
val absolutePos = absolutePos(pos)
|
val absolutePos = absolutePos(pos)
|
||||||
val player = level.randomPlayer ?: throw GameTestAssertException("Player does not exist")
|
val player = level.randomPlayer ?: throw GameTestAssertException("Player does not exist")
|
||||||
|
|
||||||
player.setupForTest()
|
player.setupForTest()
|
||||||
player.connection.teleport(absolutePos.x + 0.5, absolutePos.y + 0.5, absolutePos.z + 0.5, 0.0f, 0.0f)
|
player.connection.teleport(absolutePos.x + 0.5, absolutePos.y + 0.5, absolutePos.z + 0.5, yRot, xRot)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -78,6 +78,7 @@ object TestHooks {
|
|||||||
Loot_Test::class.java,
|
Loot_Test::class.java,
|
||||||
Modem_Test::class.java,
|
Modem_Test::class.java,
|
||||||
Monitor_Test::class.java,
|
Monitor_Test::class.java,
|
||||||
|
Pocket_Computer_Test::class.java,
|
||||||
Printer_Test::class.java,
|
Printer_Test::class.java,
|
||||||
Recipe_Test::class.java,
|
Recipe_Test::class.java,
|
||||||
Turtle_Test::class.java,
|
Turtle_Test::class.java,
|
||||||
|
@ -13,5 +13,8 @@
|
|||||||
"GameTestSequenceMixin",
|
"GameTestSequenceMixin",
|
||||||
"SharedConstantsMixin",
|
"SharedConstantsMixin",
|
||||||
"TestCommandAccessor"
|
"TestCommandAccessor"
|
||||||
|
],
|
||||||
|
"client": [
|
||||||
|
"client.MinecraftMixin"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,13 @@
|
|||||||
"""
|
"""
|
||||||
Combines screenshots from the Forge and Fabric tests into a single HTML page.
|
Combines screenshots from the Forge and Fabric tests into a single HTML page.
|
||||||
"""
|
"""
|
||||||
import os
|
|
||||||
import os.path
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from typing import TextIO
|
|
||||||
from textwrap import dedent
|
|
||||||
import webbrowser
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import pathlib
|
||||||
|
import webbrowser
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
|
from textwrap import dedent
|
||||||
|
from typing import TextIO
|
||||||
|
|
||||||
PROJECT_LOCATIONS = [
|
PROJECT_LOCATIONS = [
|
||||||
"projects/fabric",
|
"projects/fabric",
|
||||||
@ -68,7 +68,7 @@ def write_images(io: TextIO, images: list[Image]):
|
|||||||
<div class="image">
|
<div class="image">
|
||||||
<img src="../{image.path}" />
|
<img src="../{image.path}" />
|
||||||
<span class="desc">
|
<span class="desc">
|
||||||
<span class="desc-prefix">{" » ".join(image.name[:-2])} »</span>
|
<span class="desc-prefix">{" » ".join(image.name[:-1])} »</span>
|
||||||
<span class="desc-main">{image.name[-1]}</span>
|
<span class="desc-main">{image.name[-1]}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -79,6 +79,22 @@ def write_images(io: TextIO, images: list[Image]):
|
|||||||
io.write("</div></body></html>")
|
io.write("</div></body></html>")
|
||||||
|
|
||||||
|
|
||||||
|
def _normalise_id(name: str) -> str:
|
||||||
|
"""Normalise a test ID so it's more readable."""
|
||||||
|
return name[0].upper() + name[1:].replace("_", " ")
|
||||||
|
|
||||||
|
|
||||||
|
def _format_timedelta(delta: timedelta) -> str:
|
||||||
|
if delta.days > 0:
|
||||||
|
return f"{delta.days} days ago"
|
||||||
|
elif delta.seconds >= 60 * 60 * 2:
|
||||||
|
return f"{delta.seconds // (60 * 60)} hours ago"
|
||||||
|
elif delta.seconds >= 60 * 2:
|
||||||
|
return f"{delta.seconds // 60} minutes ago"
|
||||||
|
else:
|
||||||
|
return f"{delta.seconds} seconds ago"
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
spec = argparse.ArgumentParser(
|
spec = argparse.ArgumentParser(
|
||||||
description="Combines screenshots from the Forge and Fabric tests into a single HTML page."
|
description="Combines screenshots from the Forge and Fabric tests into a single HTML page."
|
||||||
@ -96,10 +112,17 @@ def main():
|
|||||||
"Forge": "projects/forge",
|
"Forge": "projects/forge",
|
||||||
"Fabric": "projects/fabric",
|
"Fabric": "projects/fabric",
|
||||||
}.items():
|
}.items():
|
||||||
dir = os.path.join(dir, "build", "testScreenshots")
|
for file in sorted(pathlib.Path(dir).glob("build/gametest/*/screenshots/*.png")):
|
||||||
for file in sorted(os.listdir(dir)):
|
name = [project, *(_normalise_id(x) for x in file.stem.split("."))]
|
||||||
name = [project, *os.path.splitext(file)[0].split(".")]
|
|
||||||
images.append(Image(name, os.path.join(dir, file)))
|
mtime = datetime.fromtimestamp(file.stat().st_mtime, tz=timezone.utc)
|
||||||
|
delta = datetime.now(tz=timezone.utc) - mtime
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"""{" » ".join(name[:-1]):>50} » \x1b[1m{name[-1]:25} \x1b[0;33m({_format_timedelta(delta)})\x1b[0m"""
|
||||||
|
)
|
||||||
|
|
||||||
|
images.append(Image(name, str(file)))
|
||||||
|
|
||||||
out_file = "build/screenshots.html"
|
out_file = "build/screenshots.html"
|
||||||
with open(out_file, encoding="utf-8", mode="w") as out:
|
with open(out_file, encoding="utf-8", mode="w") as out:
|
||||||
|
Loading…
Reference in New Issue
Block a user