mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-04-21 08:17:34 +00:00
Rework consistency check to tolerate dynamic type IDs
we don't actually care about the specific values, only whether all the blocks and their states have been correctly registered. I'd prefer to track all of the state data permutations, but the APIs for that are private, so tracking the number of permutations will have to suffice (this should be good enough to detect bugs anyway, and also takes way less space).
This commit is contained in:
parent
d5919dc094
commit
de6a91dabc
@ -24,13 +24,16 @@ declare(strict_types=1);
|
||||
namespace pocketmine\block;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use function asort;
|
||||
use function file_get_contents;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Filesystem;
|
||||
use pocketmine\utils\Utils;
|
||||
use function implode;
|
||||
use function is_array;
|
||||
use function is_int;
|
||||
use function is_string;
|
||||
use function json_decode;
|
||||
use function log;
|
||||
use function print_r;
|
||||
use const SORT_STRING;
|
||||
use const JSON_THROW_ON_ERROR;
|
||||
|
||||
class BlockTest extends TestCase{
|
||||
|
||||
@ -91,34 +94,71 @@ class BlockTest extends TestCase{
|
||||
}
|
||||
}
|
||||
|
||||
public function testConsistency() : void{
|
||||
$list = json_decode(file_get_contents(__DIR__ . '/block_factory_consistency_check.json'), true);
|
||||
if(!is_array($list)){
|
||||
throw new \pocketmine\utils\AssumptionFailedError("Old table should be array{knownStates: array<string, string>, stateDataBits: int}");
|
||||
/**
|
||||
* @return int[]
|
||||
* @phpstan-return array<string, int>
|
||||
*/
|
||||
public static function computeConsistencyCheckTable(RuntimeBlockStateRegistry $blockStateRegistry) : array{
|
||||
$newTable = [];
|
||||
|
||||
$idNameLookup = [];
|
||||
//if we ever split up block registration into multiple registries (e.g. separating chemistry blocks),
|
||||
//we'll need to ensure those additional registries are also included here
|
||||
foreach(Utils::stringifyKeys(VanillaBlocks::getAll()) as $name => $blockType){
|
||||
$id = $blockType->getTypeId();
|
||||
if(isset($idNameLookup[$id])){
|
||||
throw new AssumptionFailedError("TypeID $name collides with " . $idNameLookup[$id]);
|
||||
}
|
||||
$idNameLookup[$id] = $name;
|
||||
}
|
||||
$knownStates = [];
|
||||
/**
|
||||
* @var string $name
|
||||
* @var int[] $stateIds
|
||||
*/
|
||||
foreach($list["knownStates"] as $name => $stateIds){
|
||||
foreach($stateIds as $stateId){
|
||||
$knownStates[$stateId] = $name;
|
||||
|
||||
foreach($blockStateRegistry->getAllKnownStates() as $index => $block){
|
||||
if($index !== $block->getStateId()){
|
||||
throw new AssumptionFailedError("State index should always match state ID");
|
||||
}
|
||||
$idName = $idNameLookup[$block->getTypeId()];
|
||||
$newTable[$idName] = ($newTable[$idName] ?? 0) + 1;
|
||||
}
|
||||
|
||||
return $newTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param array<string, int> $actual
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function computeConsistencyCheckDiff(string $expectedFile, array $actual) : array{
|
||||
$expected = json_decode(Filesystem::fileGetContents($expectedFile), true, 2, JSON_THROW_ON_ERROR);
|
||||
if(!is_array($expected)){
|
||||
throw new AssumptionFailedError("Old table should be array<string, int>");
|
||||
}
|
||||
|
||||
$errors = [];
|
||||
foreach($expected as $typeName => $numStates){
|
||||
if(!is_string($typeName) || !is_int($numStates)){
|
||||
throw new AssumptionFailedError("Old table should be array<string, int>");
|
||||
}
|
||||
if(!isset($actual[$typeName])){
|
||||
$errors[] = "Removed block type $typeName ($numStates permutations)";
|
||||
}elseif($actual[$typeName] !== $numStates){
|
||||
$errors[] = "Block type $typeName permutation count changed: $numStates -> " . $actual[$typeName];
|
||||
}
|
||||
}
|
||||
foreach(Utils::stringifyKeys($actual) as $typeName => $numStates){
|
||||
if(!isset($expected[$typeName])){
|
||||
$errors[] = "Added block type $typeName (" . $actual[$typeName] . " permutations)";
|
||||
}
|
||||
}
|
||||
$oldStateDataSize = $list["stateDataBits"];
|
||||
self::assertSame($oldStateDataSize, Block::INTERNAL_STATE_DATA_BITS, "Changed number of state data bits - consistency check probably need regenerating");
|
||||
|
||||
$states = $this->blockFactory->getAllKnownStates();
|
||||
foreach($states as $stateId => $state){
|
||||
self::assertArrayHasKey($stateId, $knownStates, "New block state $stateId (" . print_r($state, true) . ") - consistency check may need regenerating");
|
||||
self::assertSame($knownStates[$stateId], $state->getName());
|
||||
}
|
||||
asort($knownStates, SORT_STRING);
|
||||
foreach($knownStates as $k => $name){
|
||||
self::assertArrayHasKey($k, $states, "Missing previously-known block state $k " . ($k >> Block::INTERNAL_STATE_DATA_BITS) . ":" . ($k & Block::INTERNAL_STATE_DATA_MASK) . " ($name)");
|
||||
self::assertSame($name, $states[$k]->getName());
|
||||
}
|
||||
return $errors;
|
||||
}
|
||||
|
||||
public function testConsistency() : void{
|
||||
$newTable = self::computeConsistencyCheckTable($this->blockFactory);
|
||||
$errors = self::computeConsistencyCheckDiff(__DIR__ . '/block_factory_consistency_check.json', $newTable);
|
||||
|
||||
self::assertEmpty($errors, "Block factory consistency check failed:\n" . implode("\n", $errors));
|
||||
}
|
||||
|
||||
public function testEmptyStateId() : void{
|
||||
|
File diff suppressed because one or more lines are too long
@ -21,86 +21,31 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\block\BlockTest;
|
||||
use pocketmine\block\RuntimeBlockStateRegistry;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Utils;
|
||||
|
||||
require dirname(__DIR__, 3) . '/vendor/autoload.php';
|
||||
|
||||
/* This script needs to be re-run after any intentional blockfactory change (adding or removing a block state). */
|
||||
|
||||
$factory = new RuntimeBlockStateRegistry();
|
||||
$remaps = [];
|
||||
$new = [];
|
||||
foreach(RuntimeBlockStateRegistry::getInstance()->getAllKnownStates() as $index => $block){
|
||||
if($index !== $block->getStateId()){
|
||||
throw new AssumptionFailedError("State index should always match state ID");
|
||||
}
|
||||
$new[$index] = $block->getName();
|
||||
}
|
||||
$newTable = BlockTest::computeConsistencyCheckTable(RuntimeBlockStateRegistry::getInstance());
|
||||
|
||||
$oldTablePath = __DIR__ . '/block_factory_consistency_check.json';
|
||||
if(file_exists($oldTablePath)){
|
||||
$oldTable = json_decode(file_get_contents($oldTablePath), true);
|
||||
if(!is_array($oldTable)){
|
||||
throw new AssumptionFailedError("Old table should be array{knownStates: array<string, string>, stateDataBits: int}");
|
||||
}
|
||||
$old = [];
|
||||
/**
|
||||
* @var string $name
|
||||
* @var int[] $stateIds
|
||||
*/
|
||||
foreach($oldTable["knownStates"] as $name => $stateIds){
|
||||
foreach($stateIds as $stateId){
|
||||
$old[$stateId] = $name;
|
||||
}
|
||||
}
|
||||
$oldStateDataSize = $oldTable["stateDataBits"];
|
||||
$oldStateDataMask = ~(~0 << $oldStateDataSize);
|
||||
$errors = BlockTest::computeConsistencyCheckDiff($oldTablePath, $newTable);
|
||||
|
||||
if($oldStateDataSize !== Block::INTERNAL_STATE_DATA_BITS){
|
||||
echo "State data bits changed from $oldStateDataSize to " . Block::INTERNAL_STATE_DATA_BITS . "\n";
|
||||
}
|
||||
|
||||
foreach($old as $k => $name){
|
||||
[$oldId, $oldStateData] = [$k >> $oldStateDataSize, $k & $oldStateDataMask];
|
||||
$reconstructedK = ($oldId << Block::INTERNAL_STATE_DATA_BITS) | $oldStateData;
|
||||
if(!isset($new[$reconstructedK])){
|
||||
echo "Removed state for $name ($oldId:$oldStateData)\n";
|
||||
}
|
||||
}
|
||||
foreach($new as $k => $name){
|
||||
[$newId, $newStateData] = [$k >> Block::INTERNAL_STATE_DATA_BITS, $k & Block::INTERNAL_STATE_DATA_MASK];
|
||||
if($newStateData > $oldStateDataMask){
|
||||
echo "Added state for $name ($newId, $newStateData)\n";
|
||||
}else{
|
||||
$reconstructedK = ($newId << $oldStateDataSize) | $newStateData;
|
||||
if(!isset($old[$reconstructedK])){
|
||||
echo "Added state for $name ($newId:$newStateData)\n";
|
||||
}elseif($old[$reconstructedK] !== $name){
|
||||
echo "Name changed ($newId:$newStateData) " . $old[$reconstructedK] . " -> " . $name . "\n";
|
||||
}
|
||||
if(count($errors) > 0){
|
||||
echo count($errors) . " changes detected:\n";
|
||||
foreach($errors as $error){
|
||||
echo $error . "\n";
|
||||
}
|
||||
}else{
|
||||
echo "No changes detected\n";
|
||||
}
|
||||
}else{
|
||||
echo "WARNING: Unable to calculate diff, no previous consistency check file found\n";
|
||||
}
|
||||
|
||||
$newTable = [];
|
||||
foreach($new as $stateId => $name){
|
||||
$newTable[$name][] = $stateId;
|
||||
}
|
||||
ksort($newTable, SORT_STRING);
|
||||
foreach(Utils::stringifyKeys($newTable) as $name => $stateIds){
|
||||
sort($stateIds, SORT_NUMERIC);
|
||||
$newTable[$name] = $stateIds;
|
||||
}
|
||||
|
||||
file_put_contents(__DIR__ . '/block_factory_consistency_check.json', json_encode(
|
||||
[
|
||||
"knownStates" => $newTable,
|
||||
"stateDataBits" => Block::INTERNAL_STATE_DATA_BITS
|
||||
],
|
||||
JSON_THROW_ON_ERROR
|
||||
));
|
||||
file_put_contents($oldTablePath, json_encode($newTable, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT));
|
||||
|
Loading…
x
Reference in New Issue
Block a user