fix: complete logical clock wall-time isolation
This commit is contained in:
@@ -18,6 +18,7 @@ final class GameClockBoundaryTest extends TestCase
|
||||
'/\bNOW\s*\(/i',
|
||||
'/\bCURRENT_TIMESTAMP\b/i',
|
||||
'/\bCURDATE\s*\(/i',
|
||||
'/\btime\s*\(/i',
|
||||
] as $pattern) {
|
||||
self::assertDoesNotMatchRegularExpression($pattern, $source, $relativePath);
|
||||
}
|
||||
@@ -25,7 +26,7 @@ final class GameClockBoundaryTest extends TestCase
|
||||
|
||||
public static function gameSchedulingFiles(): array
|
||||
{
|
||||
return array_map(static fn (string $path): array => [$path], [
|
||||
$paths = [
|
||||
'hwe/sammo/TurnExecutionHelper.php',
|
||||
'hwe/sammo/Auction.php',
|
||||
'hwe/sammo/AuctionBasicResource.php',
|
||||
@@ -36,7 +37,43 @@ final class GameClockBoundaryTest extends TestCase
|
||||
'hwe/sammo/AbsFromUserPool.php',
|
||||
'hwe/sammo/GeneralPool/RandomNameGeneral.php',
|
||||
'hwe/sammo/API/General/DieOnPrestart.php',
|
||||
]);
|
||||
'hwe/sammo/Message.php',
|
||||
'hwe/sammo/DiplomaticMessage.php',
|
||||
'hwe/sammo/ScoutMessage.php',
|
||||
'hwe/sammo/RaiseInvaderMessage.php',
|
||||
'hwe/sammo/GeneralAI.php',
|
||||
'hwe/sammo/API/Vote/NewVote.php',
|
||||
'hwe/sammo/API/Vote/Vote.php',
|
||||
'hwe/sammo/API/Vote/GetVoteList.php',
|
||||
'hwe/sammo/API/Vote/GetVoteDetail.php',
|
||||
'hwe/sammo/API/Vote/AddComment.php',
|
||||
'hwe/sammo/API/Nation/SetNotice.php',
|
||||
'hwe/j_get_select_npc_token.php',
|
||||
'hwe/j_get_select_pool.php',
|
||||
'hwe/j_set_npc_control.php',
|
||||
'hwe/j_board_article_add.php',
|
||||
'hwe/j_board_comment_add.php',
|
||||
'hwe/a_traffic.php',
|
||||
'hwe/j_server_basic_info.php',
|
||||
'hwe/j_diplomacy_send_letter.php',
|
||||
'hwe/j_diplomacy_respond_letter.php',
|
||||
'hwe/j_diplomacy_destroy_letter.php',
|
||||
'hwe/j_diplomacy_rollback_letter.php',
|
||||
];
|
||||
foreach (['hwe/sammo/Command', 'hwe/sammo/Event'] as $relativeDirectory) {
|
||||
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(
|
||||
__DIR__ . '/../' . $relativeDirectory,
|
||||
\FilesystemIterator::SKIP_DOTS,
|
||||
));
|
||||
foreach ($iterator as $file) {
|
||||
if ($file->isFile() && $file->getExtension() === 'php') {
|
||||
$paths[] = $relativeDirectory . '/' . $iterator->getSubPathName();
|
||||
}
|
||||
}
|
||||
}
|
||||
$paths = array_values(array_unique($paths));
|
||||
sort($paths);
|
||||
return array_map(static fn (string $path): array => [$path], $paths);
|
||||
}
|
||||
|
||||
public function testTickSchemaDoesNotUseDatabaseDefaultsForGameSchedules(): void
|
||||
@@ -55,5 +92,55 @@ final class GameClockBoundaryTest extends TestCase
|
||||
] as $expected) {
|
||||
self::assertStringContainsString($expected, $schema);
|
||||
}
|
||||
self::assertDoesNotMatchRegularExpression('/\b(?:CURRENT_TIMESTAMP|NOW\s*\()/i', $schema);
|
||||
}
|
||||
|
||||
public function testMonthlyTrafficTimestampUsesLogicalClock(): void
|
||||
{
|
||||
$source = file_get_contents(__DIR__ . '/../hwe/func.php');
|
||||
self::assertIsString($source);
|
||||
self::assertMatchesRegularExpression(
|
||||
'/function updateTraffic\(\).*?GameClock::fromStorage\(\$gameStor\)->formatNow\(\).*?function CheckOverhead\(/s',
|
||||
$source,
|
||||
);
|
||||
self::assertDoesNotMatchRegularExpression(
|
||||
'/function updateTraffic\(\).*?TimeUtil::now\(.*?function CheckOverhead\(/s',
|
||||
$source,
|
||||
);
|
||||
}
|
||||
|
||||
public function testGameLoginDeathCheckUsesTicksWhileSessionTtlRemainsOperational(): void
|
||||
{
|
||||
$source = file_get_contents(__DIR__ . '/../src/sammo/Session.php');
|
||||
self::assertIsString($source);
|
||||
self::assertMatchesRegularExpression(
|
||||
'/function loginGame\(.*?GameClock::fromStorage\(\$gameStor\)->nowTick\(\).*?GameClock::TICKS_PER_TURN.*?function logoutGame\(/s',
|
||||
$source,
|
||||
);
|
||||
self::assertDoesNotMatchRegularExpression(
|
||||
'/function loginGame\(.*?new\s+\\?DateTime(?:Immutable)?\([^)]*turntime.*?function logoutGame\(/s',
|
||||
$source,
|
||||
);
|
||||
}
|
||||
|
||||
public function testBrowserDoesNotCompareProjectedGameDatesToItsWallClock(): void
|
||||
{
|
||||
$expectations = [
|
||||
'hwe/ts/PageVote.vue' => ['currentVote.value.isOpen'],
|
||||
'hwe/ts/components/MessagePlate.vue' => ['msg.clockMode === "manual"'],
|
||||
'hwe/ts/gateway/entrance.ts' => ['game.isOpen'],
|
||||
'hwe/ts/select_npc.ts' => ['logicalClockRunning'],
|
||||
'hwe/ts/select_general_from_pool.ts' => ['logicalClockRunning'],
|
||||
];
|
||||
foreach ($expectations as $path => $needles) {
|
||||
$source = file_get_contents(__DIR__ . '/../' . $path);
|
||||
self::assertIsString($source);
|
||||
foreach ($needles as $needle) {
|
||||
self::assertStringContainsString($needle, $source, $path);
|
||||
}
|
||||
}
|
||||
self::assertStringNotContainsString('formatTime(new Date())', file_get_contents(__DIR__ . '/../hwe/ts/PageVote.vue'));
|
||||
self::assertStringNotContainsString('game.opentime <= now', file_get_contents(__DIR__ . '/../hwe/ts/gateway/entrance.ts'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace sammo;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
require_once __DIR__ . '/../src/sammo/GameClock.php';
|
||||
require_once __DIR__ . '/../hwe/sammo/TurnExecutionHelper.php';
|
||||
|
||||
final class GameClockTest extends TestCase
|
||||
{
|
||||
@@ -58,6 +59,7 @@ final class GameClockTest extends TestCase
|
||||
);
|
||||
|
||||
self::assertSame(123_456, $clock->nowTick());
|
||||
self::assertSame($clock->formatTick(123_456), $clock->formatNow());
|
||||
self::assertFalse($wallRead);
|
||||
}
|
||||
|
||||
@@ -96,4 +98,10 @@ final class GameClockTest extends TestCase
|
||||
$this->expectException(\OverflowException::class);
|
||||
GameClock::addTicks(GameClock::MAX_SAFE_TICK, 1);
|
||||
}
|
||||
|
||||
public function testGlobalCompletionTickIsMonotonicAcrossSubTickExecution(): void
|
||||
{
|
||||
self::assertSame(36_000_000, TurnExecutionHelper::monotonicCompletionTick(36_000_000, 35_500_000));
|
||||
self::assertSame(36_500_000, TurnExecutionHelper::monotonicCompletionTick(36_000_000, 36_500_000));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace sammo;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use sammo\DTO\VoteInfo;
|
||||
|
||||
require_once __DIR__ . '/../src/sammo/GameClock.php';
|
||||
require_once __DIR__ . '/../hwe/sammo/DTO/VoteInfo.php';
|
||||
|
||||
final class VoteGameClockTest extends TestCase
|
||||
{
|
||||
public function testLegacyDatesAreConvertedToStableTicksAndProjectedAgain(): void
|
||||
{
|
||||
$base = new \DateTimeImmutable('2035-01-01 00:00:00.000000');
|
||||
$clock = new GameClock($base, 60, 0, GameClock::MODE_MANUAL, $base);
|
||||
$raw = [
|
||||
'id' => 7,
|
||||
'title' => '논리 시계 투표',
|
||||
'multipleOptions' => 1,
|
||||
'opener' => 'SYSTEM',
|
||||
'startDate' => '2035-01-01 01:00:00',
|
||||
'endDate' => '2035-01-01 03:00:00',
|
||||
'options' => ['찬성', '반대'],
|
||||
];
|
||||
|
||||
$stored = VoteInfo::normalizeGameStorage($raw, $clock);
|
||||
|
||||
self::assertSame(GameClock::TICKS_PER_TURN, $stored['startTick']);
|
||||
self::assertSame(GameClock::TICKS_PER_TURN * 3, $stored['endTick']);
|
||||
self::assertSame('2035-01-01 01:00:00', $stored['startDate']);
|
||||
self::assertSame('2035-01-01 03:00:00', $stored['endDate']);
|
||||
}
|
||||
|
||||
public function testStoredTicksRemainAuthoritativeWhenProjectionBaseChanges(): void
|
||||
{
|
||||
$oldBase = new \DateTimeImmutable('2035-01-01 00:00:00');
|
||||
$newBase = new \DateTimeImmutable('2040-05-01 12:00:00');
|
||||
$clock = new GameClock($newBase, 60, 0, GameClock::MODE_MANUAL, $newBase);
|
||||
$stored = [
|
||||
'id' => 8,
|
||||
'title' => 'tick 우선',
|
||||
'multipleOptions' => 1,
|
||||
'opener' => null,
|
||||
'startDate' => $oldBase->format('Y-m-d H:i:s'),
|
||||
'endDate' => $oldBase->modify('+1 hour')->format('Y-m-d H:i:s'),
|
||||
'startTick' => GameClock::TICKS_PER_TURN * 2,
|
||||
'endTick' => GameClock::TICKS_PER_TURN * 4,
|
||||
'options' => ['A'],
|
||||
];
|
||||
|
||||
$normalized = VoteInfo::normalizeGameStorage($stored, $clock);
|
||||
self::assertSame('2040-05-01 14:00:00', $normalized['startDate']);
|
||||
self::assertSame('2040-05-01 16:00:00', $normalized['endDate']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user