<?php
/**
* SeekQuarry/Yioop --
* Open Source Pure PHP Search Engine, Crawler, and Indexer
*
* Copyright (C) 2009 - 2026 Chris Pollett chris@pollett.org
*
* LICENSE:
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* END LICENSE
*
* @author Chris Pollett chris@pollett.org
* @license https://www.gnu.org/licenses/ GPL3
* @link https://www.seekquarry.com/
* @copyright 2009 - 2026
* @filesource
*/
namespace seekquarry\yioop\tests;
use seekquarry\yioop\configs as C;
use seekquarry\yioop\library\storage_formats\PartitionDocumentBundle;
use seekquarry\yioop\library\UnitTest;
/**
* Tests the reading of a blob out of a partition file, where the length
* comes from the bundle's own index. A read asks the memory manager for
* the whole length before it reads anything, so a length that is wrong
* asks for that much memory whatever the file holds, and a long running
* server near its memory limit dies of one such read.
*
* @author Chris Pollett
*/
class PartitionDocumentBundleTest extends UnitTest
{
/**
* $folder stores where a case writes the partition file it reads.
* @var string
*/
public $folder = "";
/**
* $bundle stores the bundle a case reads through.
* @var object
*/
public $bundle = null;
/**
* setUp makes a partition file holding a hundred bytes, which is the
* file every case here reads from.
*/
public function setUp()
{
$this->folder = C\WORK_DIRECTORY . "/temp/blobread" . getmypid();
if (!file_exists($this->folder)) {
mkdir($this->folder, 0777, true);
@chmod($this->folder, 0777);
}
$this->bundle = new PartitionDocumentBundle($this->folder,
["PRIMARY KEY" => "KEY", "VALUE" => "BLOB"]);
file_put_contents($this->folder . "/partition0.txt",
str_repeat("small", 20));
@chmod($this->folder . "/partition0.txt", 0777);
}
/**
* tearDown takes away the partition file and the folder a case made.
*/
public function tearDown()
{
if (!file_exists($this->folder)) {
return;
}
foreach (glob($this->folder . "/*") as $one) {
unlink($one);
}
rmdir($this->folder);
}
/**
* anHonestLengthIsReadWholeTestCase checks that a length matching
* what the file holds is read as it always was, so the guard on the
* other lengths does not cost an ordinary read anything.
*/
public function anHonestLengthIsReadWholeTestCase()
{
$file = $this->folder . "/partition0.txt";
$said = PartitionDocumentBundle::lengthWorthReading($file, 0, 100);
$this->assertEqual(100, $said,
"a length the file can hold is left as it is");
$said = PartitionDocumentBundle::lengthWorthReading($file, 50, 50);
$this->assertEqual(50, $said,
"and so is one that reaches exactly the end");
}
/**
* lengthPastTheFileIsCutToWhatIsThereTestCase checks that a length
* running past the end of the file is cut to what is left, so the
* read asks for that much and no more.
*/
public function lengthPastTheFileIsCutToWhatIsThereTestCase()
{
$file = $this->folder . "/partition0.txt";
$said = PartitionDocumentBundle::lengthWorthReading($file, 0, 400);
$this->assertEqual(100, $said,
"a length past the end is cut to what the file holds");
$said = PartitionDocumentBundle::lengthWorthReading($file, 200, 50);
$this->assertEqual(0, $said,
"and an offset past the end asks for nothing at all");
}
/**
* lengthPastWhatABlobMayBeIsRefusedTestCase checks that a length
* larger than any blob is refused outright. Such a length asks the
* memory manager for that much before the file is touched, which is
* how a server near its limit is ended by one corrupt entry.
*/
public function lengthPastWhatABlobMayBeIsRefusedTestCase()
{
$file = $this->folder . "/partition0.txt";
$said = PartitionDocumentBundle::lengthWorthReading($file, 0,
C\MAX_BLOB_READ_LEN + 1);
$this->assertEqual(0, $said,
"a length past the most a blob may be asks for nothing");
$said = PartitionDocumentBundle::lengthWorthReading($file, 0,
291504352);
$this->assertEqual(0, $said,
"and so does the length that ended the live server");
}
/**
* blobIsReadWithoutAskingForMoreThanTheFileHoldsTestCase reads
* through the bundle itself and checks how much memory the read
* asks for. A read of two hundred and seventy-eight megabytes from
* a file of a hundred bytes used to ask for all of it.
*/
public function blobIsReadWithoutAskingForMoreThanTheFileHoldsTestCase()
{
$file = $this->folder . "/partition0.txt";
$before = memory_get_peak_usage(true);
$said = $this->bundle->getArchive($file, 0, 291504352);
$grew = memory_get_peak_usage(true) - $before;
$this->assertTrue($grew < C\MAX_BLOB_READ_LEN,
"the read asks for less than the most a blob may be, and " .
"asked for " . $grew . " bytes");
$this->assertTrue($said === false || strlen($said) <= 100,
"and hands back nothing more than the file holds");
}
}