mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-01-05 23:10:30 +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
|
||||
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.
|
||||
*/
|
||||
@ -109,11 +103,6 @@ abstract class ClientJavaExec : JavaExec() {
|
||||
} else {
|
||||
super.exec()
|
||||
}
|
||||
|
||||
fsOperations.copy {
|
||||
from(workingDir.resolve("screenshots"))
|
||||
into(screenshots)
|
||||
}
|
||||
}
|
||||
|
||||
@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_INFO = "UpgradeInfo";
|
||||
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 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.
|
||||
*
|
||||
* @see <https://github.com/cc-tweaked/cc-restitched/issues/121>
|
||||
* @see <https://github.com/cc-tweaked/cc-restitched/issues/122>
|
||||
*/
|
||||
@GameTest
|
||||
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
|
||||
|
||||
import dan200.computercraft.gametest.core.MinecraftExtensions
|
||||
import dan200.computercraft.mixin.gametest.GameTestSequenceAccessor
|
||||
import dan200.computercraft.shared.platform.Registries
|
||||
import net.minecraft.client.Minecraft
|
||||
@ -20,9 +21,7 @@ import java.util.concurrent.atomic.AtomicBoolean
|
||||
/**
|
||||
* Attempt to guess whether all chunks have been rendered.
|
||||
*/
|
||||
fun Minecraft.isRenderingStable(): Boolean = level != null && player != null &&
|
||||
levelRenderer.isChunkCompiled(player!!.blockPosition()) && levelRenderer.countRenderedChunks() > 10 &&
|
||||
levelRenderer.hasRenderedAllChunks()
|
||||
fun Minecraft.isRenderingStable(): Boolean = (this as MinecraftExtensions).`computercraft$isRenderingStable`()
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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 test = (this as GameTestSequenceAccessor).parent
|
||||
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
|
||||
// has caught up.
|
||||
thenOnClient { minecraft.options.hideGui = true }
|
||||
thenOnClient { minecraft.options.hideGui = !showGui }
|
||||
thenIdle(2)
|
||||
|
||||
// Take a screenshot and wait for it to have finished.
|
||||
@ -96,12 +95,12 @@ fun GameTestHelper.positionAtArmorStand() {
|
||||
/**
|
||||
* 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 player = level.randomPlayer ?: throw GameTestAssertException("Player does not exist")
|
||||
|
||||
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,
|
||||
Modem_Test::class.java,
|
||||
Monitor_Test::class.java,
|
||||
Pocket_Computer_Test::class.java,
|
||||
Printer_Test::class.java,
|
||||
Recipe_Test::class.java,
|
||||
Turtle_Test::class.java,
|
||||
|
@ -13,5 +13,8 @@
|
||||
"GameTestSequenceMixin",
|
||||
"SharedConstantsMixin",
|
||||
"TestCommandAccessor"
|
||||
],
|
||||
"client": [
|
||||
"client.MinecraftMixin"
|
||||
]
|
||||
}
|
||||
|
@ -2,13 +2,13 @@
|
||||
"""
|
||||
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 pathlib
|
||||
import webbrowser
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from textwrap import dedent
|
||||
from typing import TextIO
|
||||
|
||||
PROJECT_LOCATIONS = [
|
||||
"projects/fabric",
|
||||
@ -68,7 +68,7 @@ def write_images(io: TextIO, images: list[Image]):
|
||||
<div class="image">
|
||||
<img src="../{image.path}" />
|
||||
<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>
|
||||
</div>
|
||||
@ -79,6 +79,22 @@ def write_images(io: TextIO, images: list[Image]):
|
||||
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():
|
||||
spec = argparse.ArgumentParser(
|
||||
description="Combines screenshots from the Forge and Fabric tests into a single HTML page."
|
||||
@ -96,10 +112,17 @@ def main():
|
||||
"Forge": "projects/forge",
|
||||
"Fabric": "projects/fabric",
|
||||
}.items():
|
||||
dir = os.path.join(dir, "build", "testScreenshots")
|
||||
for file in sorted(os.listdir(dir)):
|
||||
name = [project, *os.path.splitext(file)[0].split(".")]
|
||||
images.append(Image(name, os.path.join(dir, file)))
|
||||
for file in sorted(pathlib.Path(dir).glob("build/gametest/*/screenshots/*.png")):
|
||||
name = [project, *(_normalise_id(x) for x in file.stem.split("."))]
|
||||
|
||||
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"
|
||||
with open(out_file, encoding="utf-8", mode="w") as out:
|
||||
|
Loading…
Reference in New Issue
Block a user