Merge branch 'minor-next' into major-next

This commit is contained in:
Dylan K. Taylor
2024-05-06 15:42:49 +01:00
62 changed files with 2013 additions and 416 deletions

View File

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

View File

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

View File

@ -32,8 +32,6 @@ use pocketmine\utils\MainLogger;
use function define;
use function dirname;
use function microtime;
use function sys_get_temp_dir;
use function tempnam;
use function usleep;
class AsyncPoolTest extends TestCase{
@ -45,13 +43,12 @@ class AsyncPoolTest extends TestCase{
public function setUp() : void{
@define('pocketmine\\COMPOSER_AUTOLOADER_PATH', dirname(__DIR__, 3) . '/vendor/autoload.php');
$this->mainLogger = new MainLogger(tempnam(sys_get_temp_dir(), "pmlog"), false, "Main", new \DateTimeZone('UTC'));
$this->mainLogger = new MainLogger(null, false, "Main", new \DateTimeZone('UTC'));
$this->pool = new AsyncPool(2, 1024, new ThreadSafeClassLoader(), $this->mainLogger, new SleeperHandler());
}
public function tearDown() : void{
$this->pool->shutdown();
$this->mainLogger->shutdownLogWriterThread();
}
public function testTaskLeak() : void{