summaryrefslogtreecommitdiff
path: root/sapi/fpm/tests/logtool.inc
diff options
context:
space:
mode:
Diffstat (limited to 'sapi/fpm/tests/logtool.inc')
-rw-r--r--sapi/fpm/tests/logtool.inc476
1 files changed, 476 insertions, 0 deletions
diff --git a/sapi/fpm/tests/logtool.inc b/sapi/fpm/tests/logtool.inc
new file mode 100644
index 0000000000..219c6fedbb
--- /dev/null
+++ b/sapi/fpm/tests/logtool.inc
@@ -0,0 +1,476 @@
+<?php
+
+namespace FPM;
+
+class LogTool
+{
+ const P_TIME = '\[\d\d-\w\w\w-\d{4} \d\d:\d\d:\d\d\]';
+ const P_PREFIX = '\[pool unconfined\] child \d+ said into stderr: ';
+ const FINAL_SUFFIX = ', pipe is closed';
+
+ /**
+ * @var string
+ */
+ private $message;
+
+ /**
+ * @var string
+ */
+ private $level;
+
+ /**
+ * @var int
+ */
+ private $position;
+
+ /**
+ * @var int
+ */
+ private $suffixPosition;
+
+ /**
+ * @var int
+ */
+ private $limit;
+
+ /**
+ * @var string
+ */
+ private $pattern;
+
+ /**
+ * @var string
+ */
+ private $error;
+
+ /**
+ * @param string $message
+ * @param int $limit
+ * @param int $repeat
+ */
+ public function setExpectedMessage(string $message, int $limit, int $repeat = 0)
+ {
+ $this->message = ($repeat > 0) ? str_repeat($message, $repeat) : $message;
+ $this->limit = $limit;
+ $this->position = 0;
+ }
+
+ /**
+ * @param string $level
+ * @return int
+ */
+ public function setExpectedLevel(string $level)
+ {
+ return $this->level = $level;
+ }
+
+ /**
+ * @return string
+ */
+ public function getExpectedLevel(): string
+ {
+ return $this->level ?: 'WARNING';
+ }
+
+ /**
+ * @param string $line
+ * @return bool
+ */
+ public function checkTruncatedMessage(string $line)
+ {
+ if ($this->message === null) {
+ throw new \LogicException('The message has not been set');
+ }
+ $lineLen = strlen($line);
+ if (!$this->checkLineLength($line)) {
+ return false;
+ }
+ $this->pattern = '/^PHP message: (.*?)(\.\.\.)?$/';
+ if (preg_match($this->pattern, $line, $matches) === 0) {
+ return $this->error("Unexpected truncated message: {$line}");
+ }
+
+ if ($lineLen === $this->limit) {
+ if (!isset($matches[2])) {
+ return $this->error("The truncated line is not ended with '...'");
+ }
+ if (!$this->checkMessage($matches[1])) {
+ return false;
+ }
+ } else {
+ if (isset($matches[2])) {
+ // this is expecting that the expected message does not end with '...'
+ // which should not be an issue for the test purpose.
+ return $this->error("The line is complete and should not end with '...'");
+ }
+ if (!$this->checkMessage($matches[1], -1)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @param array $lines
+ * @param bool $terminated
+ * @param bool $decorated
+ * @return bool
+ */
+ public function checkWrappedMessage(array $lines, bool $terminated = true, bool $decorated = true)
+ {
+ if ($this->message === null) {
+ throw new \LogicException('The message has not been set');
+ }
+ if ($decorated) {
+ $this->pattern = sprintf(
+ '/^(%s %s: %s)"([^"]*)"(.*)?$/',
+ self::P_TIME,
+ $this->getExpectedLevel(),
+ self::P_PREFIX
+ );
+ } else {
+ $this->pattern = null;
+ }
+
+ $idx = 0;
+ foreach ($lines as $idx => $line) {
+ if (!$this->checkLine($line)) {
+ break;
+ }
+ }
+
+ if ($this->suffixPosition > 0) {
+ $suffixPattern = sprintf(
+ '/^%s %s: %s(.*)$/',
+ self::P_TIME, $this->getExpectedLevel(),
+ self::P_PREFIX
+ );
+ $line = $lines[++$idx];
+ if (preg_match($suffixPattern, $line, $matches) === 0) {
+ return $this->error("Unexpected line: $line");
+ }
+ if ($matches[1] !== substr(self::FINAL_SUFFIX, $this->suffixPosition)) {
+ return $this->error(
+ "The suffix has not been finished from position $this->suffixPosition in line: $line"
+ );
+ }
+ }
+
+ if ($terminated) {
+ return $this->expectTerminatorLines($lines, $idx);
+ }
+
+ return true;
+ }
+
+ /**
+ * @param string $line
+ * @return bool
+ */
+ private function checkLine(string $line)
+ {
+ if ($this->pattern === null) {
+ // plain (not decorated) output
+ $out = rtrim($line);
+ $finalSuffix = null;
+ } elseif (($res = preg_match($this->pattern, $line, $matches)) > 0) {
+ $out = $matches[2];
+ $finalSuffix = $matches[3] ?? false;
+ } else {
+ return $this->error("Unexpected line: $line");
+ }
+
+ $rem = strlen($this->message) - $this->position;
+ $lineLen = strlen($line);
+ if (!$this->checkLineLength($line, $lineLen)) {
+ return false;
+ }
+ if (!$this->checkMessage($out, $this->position)) {
+ return false;
+ }
+ $outLen = strlen($out);
+ if ($rem > $outLen) { // continuous line
+ if ($lineLen !== $this->limit) {
+ if ($lineLen + ($rem - $outLen) < $this->limit) {
+ return $this->error("Printed less than the message len");
+ }
+ return $this->error(
+ "The continuous line length is $lineLen but it should equal to limit $this->limit"
+ );
+ }
+ $this->position += $outLen;
+ return true;
+ }
+ if ($rem !== $outLen) {
+ return $this->error("Printed more than the message len");
+ }
+ if ($finalSuffix === null || $finalSuffix === "") {
+ return false;
+ }
+ if ($finalSuffix === false) {
+ return $this->error("No final suffix");
+ }
+ if (strpos(self::FINAL_SUFFIX, $finalSuffix) === false) {
+ return $this->error("The final suffix has to be equal to ', pipe is closed'");
+ }
+ if (self::FINAL_SUFFIX !== $finalSuffix) {
+ $this->suffixPosition = strlen($finalSuffix);
+ }
+ // complete final suffix printed
+ return false;
+ }
+
+ /**
+ * @param string $line
+ * @param int $lineLen
+ * @return bool
+ */
+ private function checkLineLength(string $line, $lineLen = null) {
+ $lineLen = $lineLen ?: strlen($line);
+ if ($lineLen > $this->limit) {
+ return $this->error(
+ "The line length is $lineLen which is higher than limit $this->limit"
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * @param string $matchedMessage
+ * @param int $expectedMessageStart
+ * @return bool
+ */
+ private function checkMessage(string $matchedMessage, int $expectedMessageStart = 0)
+ {
+ if ($expectedMessageStart < 0) {
+ $expectedMessage = $this->message;
+ } else {
+ $expectedMessage = substr($this->message, $expectedMessageStart, strlen($matchedMessage));
+ }
+ if ($expectedMessage !== $matchedMessage) {
+ return $this->error(
+ sprintf(
+ "The actual string(%d) does not match expected string(%d):\n",
+ strlen($matchedMessage),
+ strlen($expectedMessage)
+ ) .
+ "- EXPECT: '$expectedMessage'\n" .
+ "- ACTUAL: '$matchedMessage'"
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * @param array $lines
+ * @return bool
+ */
+ public function expectStartingLines(array $lines)
+ {
+ if ($this->getError()) {
+ return false;
+ }
+
+ if (count($lines) < 2) {
+ return $this->error("No starting lines");
+ }
+
+ return (
+ $this->expectNotice($lines[0], 'fpm is running, pid \d+') &&
+ $this->expectNotice($lines[1], 'ready to handle connections')
+ );
+ }
+
+ /**
+ * @param array $lines
+ * @param int $idx
+ * @return bool
+ */
+ public function expectTerminatorLines(array $lines, int $idx = -1)
+ {
+ if ($this->getError()) {
+ return false;
+ }
+
+ if (count($lines) - $idx < 3) {
+ return $this->error("No terminating lines");
+ }
+
+ return (
+ $this->expectNotice($lines[++$idx], 'Terminating ...') &&
+ $this->expectNotice($lines[++$idx], 'exiting, bye-bye!')
+ );
+ }
+
+ /**
+ * @param string $type
+ * @param string $line
+ * @param string $expectedMessage
+ * @param string|null $pool
+ * @return bool
+ */
+ public function expectEntry(string $type, string $line, string $expectedMessage, $pool = null)
+ {
+ if ($this->getError()) {
+ return false;
+ }
+ if ($pool !== null) {
+ $expectedMessage = '\[pool ' . $pool . '\] ' . $expectedMessage;
+ }
+
+ $line = rtrim($line);
+ $pattern = sprintf('/^%s %s: %s$/', self::P_TIME, $type, $expectedMessage);
+
+ if (preg_match($pattern, $line, $matches) === 0) {
+ return $this->error(
+ "The $type does not match expected message:\n" .
+ "- PATTERN: $pattern\n" .
+ "- MESSAGE: $line\n" .
+ "- EXPECT: '$expectedMessage'\n" .
+ "- ACTUAL: '" . substr($line, strpos($line, $type) + strlen($type) + 2) . "'"
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * @param string $line
+ * @param string $expectedMessage
+ * @param string|null $pool
+ * @return bool
+ */
+ public function expectDebug(string $line, string $expectedMessage, $pool = null)
+ {
+ return $this->expectEntry('DEBUG', $line, $expectedMessage, $pool);
+ }
+
+ /**
+ * @param string $line
+ * @param string $expectedMessage
+ * @param string|null $pool
+ * @return bool
+ */
+ public function expectNotice(string $line, string $expectedMessage, $pool = null)
+ {
+ return $this->expectEntry('NOTICE', $line, $expectedMessage, $pool);
+ }
+
+ /**
+ * @param string $line
+ * @param string $expectedMessage
+ * @param string|null $pool
+ * @return bool
+ */
+ public function expectWarning(string $line, string $expectedMessage, $pool = null)
+ {
+ return $this->expectEntry('WARNING', $line, $expectedMessage, $pool);
+ }
+
+ /**
+ * @param string $line
+ * @param string $expectedMessage
+ * @param string|null $pool
+ * @return bool
+ */
+ public function expectError(string $line, string $expectedMessage, $pool = null)
+ {
+ return $this->expectEntry('ERROR', $line, $expectedMessage, $pool);
+ }
+
+ /**
+ * @param string $line
+ * @param string $expectedMessage
+ * @param string|null $pool
+ * @return bool
+ */
+ public function expectAlert(string $line, string $expectedMessage, $pool = null)
+ {
+ return $this->expectEntry('ALERT', $line, $expectedMessage, $pool);
+ }
+
+
+ /**
+ * @param string $msg
+ * @return bool
+ */
+ private function error(string $msg)
+ {
+ $this->error = $msg;
+ echo "ERROR: $msg\n";
+ return false;
+ }
+
+ /**
+ * @return string
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+}
+
+if (isset($argv[1]) && $argv[1] === 'logtool-selftest') {
+ $cases = [
+ [
+ 'limit' => 1050,
+ 'lines' => [
+ '[08-Oct-2017 19:53:50] WARNING: [pool unconfined] child 23183 said into stderr: "' .
+ str_repeat('a', 968) . '"',
+ '[08-Oct-2017 19:53:50] WARNING: [pool unconfined] child 23183 said into stderr: "' .
+ str_repeat('a', 968) . '"',
+ '[08-Oct-2017 19:53:50] WARNING: [pool unconfined] child 23183 said into stderr: "' .
+ str_repeat('a', 112) . '", pipe is closed',
+ '[08-Oct-2017 19:53:55] NOTICE: Terminating ...',
+ '[08-Oct-2017 19:53:55] NOTICE: exiting, bye-bye!',
+ ],
+ 'message' => str_repeat('a', 2048),
+ 'type' => 'stdio',
+ ],
+ [
+ 'limit' => 1050,
+ 'lines' => [
+ '[08-Oct-2017 19:53:50] WARNING: [pool unconfined] child 23183 said into stderr: "' .
+ str_repeat('a', 968) . '"',
+ '[08-Oct-2017 19:53:50] WARNING: [pool unconfined] child 23183 said into stderr: "' .
+ str_repeat('a', 968) . '"',
+ '[08-Oct-2017 19:53:50] WARNING: [pool unconfined] child 23183 said into stderr: "' .
+ str_repeat('a', 964) . '", pi',
+ '[08-Oct-2017 19:53:50] WARNING: [pool unconfined] child 23183 said into stderr: pe is closed',
+ '[08-Oct-2017 19:53:55] NOTICE: Terminating ...',
+ '[08-Oct-2017 19:53:55] NOTICE: exiting, bye-bye!',
+ ],
+ 'message' => str_repeat('a', 2900),
+ 'type' => 'stdio',
+ ],
+ [
+ 'limit' => 1024,
+ 'line' => '[08-Oct-2017 19:53:50] WARNING: ' . str_repeat('a',989) . '...',
+ 'message' => str_repeat('a', 2900),
+ 'type' => 'message',
+ ],
+ [
+ 'limit' => 1024,
+ 'line' => '[08-Oct-2017 19:53:50] WARNING: ' . str_repeat('a',20),
+ 'message' => str_repeat('a', 20),
+ 'type' => 'message',
+ ],
+ ];
+ foreach ($cases as $case) {
+ printf("Test message with len %d and limit %d: ", strlen($case['message']), $case['limit']);
+ $logTool = new LogTool();
+ $logTool->setExpectedMessage($case['message'], $case['limit']);
+ if ($case['type'] === 'stdio') {
+ $logTool->checkWrappedMessage($case['lines']);
+ } else {
+ $logTool->checkTruncatedMessage($case['line']);
+ }
+ if (!$logTool->getError()) {
+ echo "OK\n";
+ }
+ }
+ echo "Done\n";
+}