This commit is contained in:
2025-07-20 04:08:08 -04:00
commit e0ca63ebdf
48 changed files with 7913 additions and 0 deletions

View File

@@ -0,0 +1,150 @@
<?php
declare(strict_types=1);
namespace SqrtSpace\SpaceTime\Tests\Algorithms;
use PHPUnit\Framework\TestCase;
use SqrtSpace\SpaceTime\Algorithms\ExternalGroupBy;
use SqrtSpace\SpaceTime\SpaceTimeConfig;
class ExternalGroupByTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
SpaceTimeConfig::configure([
'external_storage_path' => sys_get_temp_dir() . '/spacetime_test',
]);
}
protected function tearDown(): void
{
$path = sys_get_temp_dir() . '/spacetime_test';
if (is_dir($path)) {
array_map('unlink', glob("$path/*"));
rmdir($path);
}
parent::tearDown();
}
public function testBasicGroupBy(): void
{
$data = [
['category' => 'A', 'value' => 1],
['category' => 'B', 'value' => 2],
['category' => 'A', 'value' => 3],
['category' => 'B', 'value' => 4],
['category' => 'C', 'value' => 5],
];
$grouped = ExternalGroupBy::groupBy($data, fn($item) => $item['category']);
$this->assertCount(3, $grouped);
$this->assertCount(2, $grouped['A']);
$this->assertCount(2, $grouped['B']);
$this->assertCount(1, $grouped['C']);
$this->assertEquals(1, $grouped['A'][0]['value']);
$this->assertEquals(3, $grouped['A'][1]['value']);
}
public function testGroupByCount(): void
{
$data = [
['type' => 'foo'],
['type' => 'bar'],
['type' => 'foo'],
['type' => 'baz'],
['type' => 'foo'],
];
$counts = ExternalGroupBy::groupByCount($data, fn($item) => $item['type']);
$this->assertEquals(3, $counts['foo']);
$this->assertEquals(1, $counts['bar']);
$this->assertEquals(1, $counts['baz']);
}
public function testGroupBySum(): void
{
$data = [
['group' => 'A', 'amount' => 10],
['group' => 'B', 'amount' => 20],
['group' => 'A', 'amount' => 15],
['group' => 'B', 'amount' => 25],
];
$sums = ExternalGroupBy::groupBySum(
$data,
fn($item) => $item['group'],
fn($item) => $item['amount']
);
$this->assertEquals(25, $sums['A']);
$this->assertEquals(45, $sums['B']);
}
public function testGroupByAggregate(): void
{
$data = [
['user' => 'john', 'score' => 80],
['user' => 'jane', 'score' => 90],
['user' => 'john', 'score' => 85],
['user' => 'jane', 'score' => 95],
];
$maxScores = ExternalGroupBy::groupByAggregate(
$data,
fn($item) => $item['user'],
fn($max, $item) => max($max ?? 0, $item['score']),
0
);
$this->assertEquals(85, $maxScores['john']);
$this->assertEquals(95, $maxScores['jane']);
}
public function testGroupByStreaming(): void
{
$data = [];
for ($i = 0; $i < 100; $i++) {
$data[] = [
'group' => chr(65 + ($i % 5)), // A-E
'value' => $i,
];
}
$groups = [];
foreach (ExternalGroupBy::groupByStreaming($data, fn($item) => $item['group']) as $key => $items) {
$groups[$key] = count($items);
}
$this->assertCount(5, $groups);
$this->assertEquals(20, $groups['A']);
$this->assertEquals(20, $groups['B']);
}
public function testGroupByWithLimit(): void
{
$data = [];
for ($i = 0; $i < 50; $i++) {
$data[] = ['key' => "group_$i", 'value' => $i];
}
$grouped = ExternalGroupBy::groupByWithLimit(
$data,
fn($item) => $item['key'],
5 // Small limit to force external storage
);
$this->assertCount(50, $grouped);
foreach ($grouped as $key => $items) {
$this->assertCount(1, $items);
$this->assertEquals($key, $items[0]['key']);
}
}
}

View File

@@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
namespace SqrtSpace\SpaceTime\Tests\Algorithms;
use PHPUnit\Framework\TestCase;
use SqrtSpace\SpaceTime\Algorithms\ExternalSort;
use SqrtSpace\SpaceTime\SpaceTimeConfig;
class ExternalSortTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
SpaceTimeConfig::configure([
'external_storage_path' => sys_get_temp_dir() . '/spacetime_test',
]);
}
protected function tearDown(): void
{
$path = sys_get_temp_dir() . '/spacetime_test';
if (is_dir($path)) {
array_map('unlink', glob("$path/*"));
rmdir($path);
}
parent::tearDown();
}
public function testBasicSort(): void
{
$data = [5, 2, 8, 1, 9, 3, 7, 4, 6];
$sorted = ExternalSort::sort($data);
$this->assertEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], $sorted);
}
public function testSortWithCustomComparator(): void
{
$data = [5, 2, 8, 1, 9, 3, 7, 4, 6];
$sorted = ExternalSort::sort($data, fn($a, $b) => $b <=> $a);
$this->assertEquals([9, 8, 7, 6, 5, 4, 3, 2, 1], $sorted);
}
public function testSortBy(): void
{
$data = [
['name' => 'John', 'age' => 25],
['name' => 'Jane', 'age' => 30],
['name' => 'Bob', 'age' => 20],
];
$sorted = ExternalSort::sortBy($data, fn($item) => $item['age']);
$this->assertEquals('Bob', $sorted[0]['name']);
$this->assertEquals('John', $sorted[1]['name']);
$this->assertEquals('Jane', $sorted[2]['name']);
}
public function testLargeDataSet(): void
{
// Generate large dataset
$data = [];
for ($i = 0; $i < 20000; $i++) {
$data[] = mt_rand(1, 100000);
}
$sorted = ExternalSort::sort($data);
// Verify it's sorted
for ($i = 1; $i < count($sorted); $i++) {
$this->assertGreaterThanOrEqual($sorted[$i - 1], $sorted[$i]);
}
// Verify same elements
$this->assertEquals(count($data), count($sorted));
sort($data);
$this->assertEquals($data, $sorted);
}
public function testSortObjects(): void
{
$objects = [
(object)['id' => 3, 'value' => 'c'],
(object)['id' => 1, 'value' => 'a'],
(object)['id' => 2, 'value' => 'b'],
];
$sorted = ExternalSort::sortBy($objects, fn($obj) => $obj->id);
$this->assertEquals(1, $sorted[0]->id);
$this->assertEquals(2, $sorted[1]->id);
$this->assertEquals(3, $sorted[2]->id);
}
public function testStreamingSort(): void
{
$data = range(10, 1);
$result = [];
foreach (ExternalSort::sortStreaming($data) as $item) {
$result[] = $item;
}
$this->assertEquals(range(1, 10), $result);
}
}

View File

@@ -0,0 +1,132 @@
<?php
declare(strict_types=1);
namespace SqrtSpace\SpaceTime\Tests\Batch;
use PHPUnit\Framework\TestCase;
use SqrtSpace\SpaceTime\Batch\BatchProcessor;
use SqrtSpace\SpaceTime\Batch\BatchResult;
class BatchProcessorTest extends TestCase
{
public function testBasicBatchProcessing(): void
{
$processor = new BatchProcessor([
'batch_size' => 3,
'checkpoint_enabled' => false,
]);
$items = range(1, 10);
$result = $processor->process($items, function($batch) {
$processed = [];
foreach ($batch as $key => $item) {
$processed[$key] = $item * 2;
}
return $processed;
});
$this->assertEquals(10, $result->getProcessedCount());
$this->assertEquals(10, $result->getSuccessCount());
$this->assertEquals(0, $result->getErrorCount());
// Check results
$results = $result->getResults();
$this->assertEquals(2, $results[0]);
$this->assertEquals(20, $results[9]);
}
public function testBatchProcessingWithErrors(): void
{
$processor = new BatchProcessor([
'batch_size' => 2,
'checkpoint_enabled' => false,
'max_retries' => 1,
]);
$items = range(1, 5);
$result = $processor->process($items, function($batch) {
$processed = [];
foreach ($batch as $key => $item) {
if ($item === 3) {
throw new \Exception('Error processing item 3');
}
$processed[$key] = $item * 2;
}
return $processed;
});
$this->assertEquals(5, $result->getProcessedCount());
$this->assertEquals(3, $result->getSuccessCount());
$this->assertEquals(2, $result->getErrorCount());
$errors = $result->getErrors();
$this->assertArrayHasKey(2, $errors); // Item 3 is at index 2
}
public function testProgressCallback(): void
{
$progressCalls = [];
$processor = new BatchProcessor([
'batch_size' => 2,
'checkpoint_enabled' => false,
'progress_callback' => function($batchNumber, $batchSize, $result) use (&$progressCalls) {
$progressCalls[] = [
'batch' => $batchNumber,
'size' => $batchSize,
'processed' => $result->getProcessedCount(),
];
},
]);
$items = range(1, 5);
$processor->process($items, fn($batch) => $batch);
$this->assertCount(3, $progressCalls); // 5 items in batches of 2
$this->assertEquals(0, $progressCalls[0]['batch']);
$this->assertEquals(2, $progressCalls[0]['size']);
}
public function testBatchResult(): void
{
$result = new BatchResult();
$result->addSuccess('key1', 'value1');
$result->addSuccess('key2', 'value2');
$result->addError('key3', new \Exception('Error'));
$this->assertEquals(3, $result->getProcessedCount());
$this->assertEquals(2, $result->getSuccessCount());
$this->assertEquals(1, $result->getErrorCount());
$this->assertTrue($result->isProcessed('key1'));
$this->assertFalse($result->isComplete());
$this->assertEquals('value1', $result->getResult('key1'));
$this->assertNotNull($result->getError('key3'));
$summary = $result->getSummary();
$this->assertEquals(3, $summary['total_processed']);
$this->assertGreaterThan(0, $summary['execution_time']);
}
public function testCheckpointingState(): void
{
$result = new BatchResult();
$result->addSuccess('a', 1);
$result->addSuccess('b', 2);
$state = $result->getState();
$newResult = new BatchResult();
$newResult->restore($state);
$this->assertEquals(2, $newResult->getProcessedCount());
$this->assertEquals(1, $newResult->getResult('a'));
$this->assertEquals(2, $newResult->getResult('b'));
}
}

View File

@@ -0,0 +1,220 @@
<?php
declare(strict_types=1);
namespace SqrtSpace\SpaceTime\Tests\Checkpoint;
use PHPUnit\Framework\TestCase;
use SqrtSpace\SpaceTime\Checkpoint\CheckpointManager;
use SqrtSpace\SpaceTime\Checkpoint\CheckpointStorage;
use SqrtSpace\SpaceTime\SpaceTimeConfig;
class CheckpointManagerTest extends TestCase
{
private CheckpointManager $manager;
private CheckpointStorage $mockStorage;
protected function setUp(): void
{
parent::setUp();
// Create a mock storage
$this->mockStorage = $this->createMock(CheckpointStorage::class);
$this->manager = new CheckpointManager('test-checkpoint', $this->mockStorage);
}
public function testShouldCheckpointReturnsFalseWhenCheckpointingDisabled(): void
{
// Mock the static config method
$this->assertFalse($this->manager->shouldCheckpoint());
}
public function testShouldCheckpointReturnsTrueAfterInterval(): void
{
// This test would need to mock SpaceTimeConfig::isCheckpointingEnabled()
// and handle time-based logic
$this->markTestSkipped('Requires static method mocking for SpaceTimeConfig');
}
public function testSaveStoresCheckpointData(): void
{
$testData = ['progress' => 50, 'items_processed' => 100];
$this->mockStorage
->expects($this->once())
->method('save')
->with(
$this->equalTo('test-checkpoint'),
$this->callback(function ($checkpoint) use ($testData) {
return $checkpoint['id'] === 'test-checkpoint' &&
isset($checkpoint['timestamp']) &&
$checkpoint['data'] === $testData;
})
);
$this->manager->save($testData);
}
public function testLoadReturnsCheckpointData(): void
{
$testData = ['progress' => 75, 'items_processed' => 150];
$checkpoint = [
'id' => 'test-checkpoint',
'timestamp' => time(),
'data' => $testData,
];
$this->mockStorage
->expects($this->once())
->method('load')
->with('test-checkpoint')
->willReturn($checkpoint);
$loadedData = $this->manager->load();
$this->assertEquals($testData, $loadedData);
}
public function testLoadReturnsNullWhenNoCheckpoint(): void
{
$this->mockStorage
->expects($this->once())
->method('load')
->with('test-checkpoint')
->willReturn(null);
$this->assertNull($this->manager->load());
}
public function testLoadReturnsNullWhenCheckpointHasNoData(): void
{
$checkpoint = [
'id' => 'test-checkpoint',
'timestamp' => time(),
// No 'data' key
];
$this->mockStorage
->expects($this->once())
->method('load')
->with('test-checkpoint')
->willReturn($checkpoint);
$this->assertNull($this->manager->load());
}
public function testExistsReturnsStorageResult(): void
{
$this->mockStorage
->expects($this->once())
->method('exists')
->with('test-checkpoint')
->willReturn(true);
$this->assertTrue($this->manager->exists());
$this->mockStorage
->expects($this->once())
->method('exists')
->with('test-checkpoint')
->willReturn(false);
$this->assertFalse($this->manager->exists());
}
public function testDeleteRemovesCheckpoint(): void
{
$this->mockStorage
->expects($this->once())
->method('delete')
->with('test-checkpoint');
$this->manager->delete();
}
public function testSetIntervalUpdatesCheckpointInterval(): void
{
// Test minimum interval of 1 second
$this->manager->setInterval(0);
// We can't directly test the interval value since it's private,
// but we can ensure the method doesn't throw an exception
$this->assertTrue(true);
$this->manager->setInterval(120);
$this->assertTrue(true);
}
public function testWrapExecutesOperationWithInitialState(): void
{
$initialState = ['counter' => 0];
$expectedResult = 'operation completed';
$this->mockStorage
->expects($this->once())
->method('load')
->willReturn(null); // No existing checkpoint
$this->mockStorage
->expects($this->once())
->method('delete')
->with('test-checkpoint');
$operation = function ($state, $manager) use ($expectedResult) {
$this->assertEquals(['counter' => 0], $state);
$this->assertInstanceOf(CheckpointManager::class, $manager);
return $expectedResult;
};
$result = $this->manager->wrap($operation, $initialState);
$this->assertEquals($expectedResult, $result);
}
public function testWrapResumesFromCheckpoint(): void
{
$checkpointData = ['counter' => 50];
$checkpoint = [
'id' => 'test-checkpoint',
'timestamp' => time(),
'data' => $checkpointData,
];
$this->mockStorage
->expects($this->once())
->method('load')
->willReturn($checkpoint);
$this->mockStorage
->expects($this->once())
->method('delete')
->with('test-checkpoint');
$operation = function ($state, $manager) {
$this->assertEquals(['counter' => 50], $state);
return 'resumed and completed';
};
$result = $this->manager->wrap($operation, ['counter' => 0]);
$this->assertEquals('resumed and completed', $result);
}
public function testWrapPreservesCheckpointOnException(): void
{
$this->mockStorage
->expects($this->once())
->method('load')
->willReturn(null);
// Delete should NOT be called when exception is thrown
$this->mockStorage
->expects($this->never())
->method('delete');
$operation = function ($state, $manager) {
throw new \RuntimeException('Operation failed');
};
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('Operation failed');
$this->manager->wrap($operation);
}
}

View File

@@ -0,0 +1,135 @@
<?php
declare(strict_types=1);
namespace SqrtSpace\SpaceTime\Tests\Collections;
use PHPUnit\Framework\TestCase;
use SqrtSpace\SpaceTime\Collections\SpaceTimeArray;
use SqrtSpace\SpaceTime\SpaceTimeConfig;
class SpaceTimeArrayTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
// Configure for testing
SpaceTimeConfig::configure([
'memory_limit' => '10M',
'external_storage_path' => sys_get_temp_dir() . '/spacetime_test',
]);
}
protected function tearDown(): void
{
// Clean up test files
$path = sys_get_temp_dir() . '/spacetime_test';
if (is_dir($path)) {
array_map('unlink', glob("$path/*"));
rmdir($path);
}
parent::tearDown();
}
public function testBasicArrayOperations(): void
{
$array = new SpaceTimeArray(100);
// Test set and get
$array['key1'] = 'value1';
$this->assertEquals('value1', $array['key1']);
// Test isset
$this->assertTrue(isset($array['key1']));
$this->assertFalse(isset($array['key2']));
// Test unset
unset($array['key1']);
$this->assertFalse(isset($array['key1']));
// Test count
$array['a'] = 1;
$array['b'] = 2;
$this->assertEquals(2, count($array));
}
public function testSpilloverToExternalStorage(): void
{
$array = new SpaceTimeArray(2); // Small threshold
// Add items that will stay in memory
$array['hot1'] = 'value1';
$array['hot2'] = 'value2';
// This should trigger spillover
$array['cold1'] = 'value3';
$array['cold2'] = 'value4';
// All items should still be accessible
$this->assertEquals('value1', $array['hot1']);
$this->assertEquals('value3', $array['cold1']);
// Count should include all items
$this->assertEquals(4, count($array));
}
public function testIterator(): void
{
$array = new SpaceTimeArray(2);
$data = ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4];
foreach ($data as $key => $value) {
$array[$key] = $value;
}
// Test iteration
$result = [];
foreach ($array as $key => $value) {
$result[$key] = $value;
}
$this->assertEquals($data, $result);
}
public function testLargeDataSet(): void
{
$array = new SpaceTimeArray(100);
// Add 1000 items
for ($i = 0; $i < 1000; $i++) {
$array["key_$i"] = "value_$i";
}
// Verify count
$this->assertEquals(1000, count($array));
// Verify random access
$this->assertEquals('value_500', $array['key_500']);
$this->assertEquals('value_999', $array['key_999']);
$this->assertEquals('value_0', $array['key_0']);
}
public function testArrayMethods(): void
{
$array = new SpaceTimeArray(10);
$array['a'] = 1;
$array['b'] = 2;
$array['c'] = 3;
// Test toArray
$this->assertEquals(['a' => 1, 'b' => 2, 'c' => 3], $array->toArray());
// Test keys
$this->assertEquals(['a', 'b', 'c'], $array->keys());
// Test values
$this->assertEquals([1, 2, 3], $array->values());
// Test clear
$array->clear();
$this->assertEquals(0, count($array));
}
}

View File

@@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace SqrtSpace\SpaceTime\Tests\Memory;
use PHPUnit\Framework\TestCase;
use SqrtSpace\SpaceTime\Memory\MemoryPressureMonitor;
use SqrtSpace\SpaceTime\Memory\MemoryPressureLevel;
use SqrtSpace\SpaceTime\Memory\MemoryPressureHandler;
class MemoryPressureMonitorTest extends TestCase
{
public function testMemoryPressureLevels(): void
{
$monitor = new MemoryPressureMonitor('100M');
// Get current level
$level = $monitor->getCurrentLevel();
$this->assertInstanceOf(MemoryPressureLevel::class, $level);
}
public function testMemoryInfo(): void
{
$monitor = new MemoryPressureMonitor();
$info = $monitor->getMemoryInfo();
$this->assertArrayHasKey('limit', $info);
$this->assertArrayHasKey('usage', $info);
$this->assertArrayHasKey('percentage', $info);
$this->assertArrayHasKey('available', $info);
$this->assertGreaterThan(0, $info['limit']);
$this->assertGreaterThanOrEqual(0, $info['usage']);
$this->assertGreaterThanOrEqual(0, $info['percentage']);
$this->assertLessThanOrEqual(100, $info['percentage']);
}
public function testHandlerRegistration(): void
{
$monitor = new MemoryPressureMonitor();
$handlerCalled = false;
$handler = new class($handlerCalled) implements MemoryPressureHandler {
private $called;
public function __construct(&$called)
{
$this->called = &$called;
}
public function shouldHandle(MemoryPressureLevel $level): bool
{
return true;
}
public function handle(MemoryPressureLevel $level, array $memoryInfo): void
{
$this->called = true;
}
};
$monitor->registerHandler($handler);
$monitor->check();
$this->assertTrue($handlerCalled);
}
public function testMemoryLimitParsing(): void
{
// Test various memory limit formats
$testCases = [
'256M' => 256 * 1024 * 1024,
'1G' => 1024 * 1024 * 1024,
'512K' => 512 * 1024,
'1024' => 1024,
];
foreach ($testCases as $limit => $expected) {
$monitor = new MemoryPressureMonitor($limit);
$info = $monitor->getMemoryInfo();
$this->assertEquals($expected, $info['limit']);
}
}
public function testPressureLevelComparison(): void
{
$this->assertTrue(MemoryPressureLevel::HIGH->isHigherThan(MemoryPressureLevel::MEDIUM));
$this->assertTrue(MemoryPressureLevel::CRITICAL->isHigherThan(MemoryPressureLevel::HIGH));
$this->assertFalse(MemoryPressureLevel::LOW->isHigherThan(MemoryPressureLevel::MEDIUM));
$this->assertFalse(MemoryPressureLevel::NONE->isHigherThan(MemoryPressureLevel::LOW));
}
}

View File

@@ -0,0 +1,161 @@
<?php
declare(strict_types=1);
namespace SqrtSpace\SpaceTime\Tests\Streams;
use PHPUnit\Framework\TestCase;
use SqrtSpace\SpaceTime\Streams\SpaceTimeStream;
class SpaceTimeStreamTest extends TestCase
{
private string $testFile;
private string $testCsv;
protected function setUp(): void
{
parent::setUp();
$this->testFile = sys_get_temp_dir() . '/test_stream.txt';
file_put_contents($this->testFile, "line1\nline2\nline3\nline4\nline5");
$this->testCsv = sys_get_temp_dir() . '/test_stream.csv';
file_put_contents($this->testCsv, "name,age\nJohn,25\nJane,30\nBob,20");
}
protected function tearDown(): void
{
if (file_exists($this->testFile)) {
unlink($this->testFile);
}
if (file_exists($this->testCsv)) {
unlink($this->testCsv);
}
parent::tearDown();
}
public function testFromArray(): void
{
$data = [1, 2, 3, 4, 5];
$result = SpaceTimeStream::from($data)->toArray();
$this->assertEquals($data, $result);
}
public function testMap(): void
{
$data = [1, 2, 3, 4, 5];
$result = SpaceTimeStream::from($data)
->map(fn($x) => $x * 2)
->toArray();
$this->assertEquals([2, 4, 6, 8, 10], $result);
}
public function testFilter(): void
{
$data = [1, 2, 3, 4, 5];
$result = SpaceTimeStream::from($data)
->filter(fn($x) => $x % 2 === 0)
->toArray();
$this->assertEquals([1 => 2, 3 => 4], $result);
}
public function testChaining(): void
{
$data = range(1, 10);
$result = SpaceTimeStream::from($data)
->filter(fn($x) => $x % 2 === 0)
->map(fn($x) => $x * 2)
->take(3)
->toArray();
$expected = [1 => 4, 3 => 8, 5 => 12];
$this->assertEquals($expected, $result);
}
public function testFromFile(): void
{
$lines = SpaceTimeStream::fromFile($this->testFile)->toArray();
$this->assertEquals(['line1', 'line2', 'line3', 'line4', 'line5'], $lines);
}
public function testFromCsv(): void
{
$rows = SpaceTimeStream::fromCsv($this->testCsv)->toArray();
$expected = [
['name' => 'John', 'age' => '25'],
['name' => 'Jane', 'age' => '30'],
['name' => 'Bob', 'age' => '20'],
];
$this->assertEquals($expected, $rows);
}
public function testReduce(): void
{
$sum = SpaceTimeStream::from([1, 2, 3, 4, 5])
->reduce(fn($acc, $x) => $acc + $x, 0);
$this->assertEquals(15, $sum);
}
public function testCount(): void
{
$count = SpaceTimeStream::from(range(1, 100))
->filter(fn($x) => $x % 2 === 0)
->count();
$this->assertEquals(50, $count);
}
public function testChunk(): void
{
$chunks = SpaceTimeStream::from(range(1, 10))
->chunk(3)
->toArray();
$this->assertCount(4, $chunks);
$this->assertEquals([1, 2, 3], $chunks[0]);
$this->assertEquals([4, 5, 6], $chunks[1]);
$this->assertEquals([7, 8, 9], $chunks[2]);
$this->assertEquals([10], $chunks[3]);
}
public function testFlatMap(): void
{
$data = [[1, 2], [3, 4], [5]];
$result = SpaceTimeStream::from($data)
->flatMap(fn($arr) => $arr)
->toArray();
$this->assertEquals([1, 2, 3, 4, 5], array_values($result));
}
public function testSkip(): void
{
$result = SpaceTimeStream::from(range(1, 10))
->skip(5)
->toArray();
$this->assertEquals([6, 7, 8, 9, 10], array_values($result));
}
public function testWriteToFile(): void
{
$outputFile = sys_get_temp_dir() . '/output_stream.txt';
SpaceTimeStream::from(['a', 'b', 'c'])
->map(fn($x) => strtoupper($x))
->toFile($outputFile);
$content = file_get_contents($outputFile);
$this->assertEquals("A\nB\nC\n", $content);
unlink($outputFile);
}
}