1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-06-17 02:40:06 +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:
Jonathan Coates 2022-11-21 21:34:35 +00:00
parent 0fc78acd49
commit 3a96aea894
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
10 changed files with 216 additions and 32 deletions

View File

@ -40,12 +40,6 @@ val clientDebug get() = project.hasProperty("clientDebug")
@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 @@ override fun exec() {
} else {
super.exec()
}
fsOperations.copy {
from(workingDir.resolve("screenshots"))
into(screenshots)
}
}
@get:Inject

View File

@ -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;

View File

@ -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();
}

View File

@ -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();
}
}

View File

@ -44,7 +44,7 @@ fun Checks_valid_item(helper: GameTestHelper) = helper.sequence {
/**
* 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 {

View File

@ -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)
}
}

View File

@ -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 @@
/**
* 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 @@
/**
* 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 @@
// 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 @@
/**
* 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)
}
/**

View File

@ -78,6 +78,7 @@ fun onServerStarted(server: MinecraftServer) {
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,

View File

@ -13,5 +13,8 @@
"GameTestSequenceMixin",
"SharedConstantsMixin",
"TestCommandAccessor"
],
"client": [
"client.MinecraftMixin"
]
}

View File

@ -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: