diff --git a/.github/workflows/job-tests.yml b/.github/workflows/job-tests.yml index e7fa84299..4b9a445c3 100644 --- a/.github/workflows/job-tests.yml +++ b/.github/workflows/job-tests.yml @@ -55,6 +55,11 @@ jobs: - "8.5" operating-system: - "blacksmith-4vcpu-ubuntu-2404" + exclude: + - dependencies: "lowest" + php-version: "8.4" + - dependencies: "lowest" + php-version: "8.5" steps: - name: "Checkout" @@ -95,7 +100,7 @@ jobs: with: php-version: "${{ matrix.php-version }}" dependencies: "${{ matrix.dependencies }}" - coverage: ${{ matrix.php-version == '8.3' && 'pcov' || 'none' }} + coverage: ${{ (matrix.php-version == '8.3' && matrix.dependencies == 'locked') && 'pcov' || 'none' }} extensions: ':psr, apcu, bcmath, dom, hash, json, mbstring, xml, xmlwriter, xmlreader, zlib, curl, pgsql, grpc, protobuf' ini-values: 'memory_limit=-1, post_max_size=32M, upload_max_filesize=32M, apc.enable_cli=1' apt-packages: "build-essential autoconf automake libtool protobuf-compiler libprotobuf-c-dev" @@ -129,7 +134,7 @@ jobs: # exercised here and the process shuts down cleanly. Must pass. - name: "Test" timeout-minutes: 15 - run: "just test --exclude-group grpc --log-junit ./var/phpunit/logs/junit.xml ${{ matrix.php-version == '8.3' && '--coverage-clover=./var/phpunit/coverage/clover/coverage.xml' || '' }}" + run: "just test --exclude-group grpc --log-junit ./var/phpunit/logs/junit.xml ${{ (matrix.php-version == '8.3' && matrix.dependencies == 'locked') && '--coverage-clover=./var/phpunit/coverage/clover/coverage.xml' || '' }}" env: PGSQL_DATABASE_URL: pgsql://postgres:postgres@127.0.0.1:${{ job.services.postgres.ports['5432'] }}/postgres?serverVersion=18&charset=utf8 MYSQL_DATABASE_URL: mysql://mysql:mysql@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/mysql @@ -155,7 +160,7 @@ jobs: - name: "Test (grpc)" timeout-minutes: 10 continue-on-error: ${{ matrix.php-version == '8.5' }} - run: "just test --group grpc --log-junit ./var/phpunit/logs/grpc-junit.xml ${{ matrix.php-version == '8.3' && '--coverage-clover=./var/phpunit/coverage/clover/grpc-coverage.xml' || '' }}" + run: "just test --group grpc --log-junit ./var/phpunit/logs/grpc-junit.xml ${{ (matrix.php-version == '8.3' && matrix.dependencies == 'locked') && '--coverage-clover=./var/phpunit/coverage/clover/grpc-coverage.xml' || '' }}" env: PGSQL_DATABASE_URL: pgsql://postgres:postgres@127.0.0.1:${{ job.services.postgres.ports['5432'] }}/postgres?serverVersion=18&charset=utf8 MYSQL_DATABASE_URL: mysql://mysql:mysql@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/mysql diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index 4770ddb2a..00e258f36 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -38,7 +38,7 @@ jobs: tests: uses: ./.github/workflows/job-tests.yml with: - dependencies: ${{ (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && '["locked","lowest","highest"]' || '["locked"]' }} + dependencies: ${{ (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && '["locked","lowest","highest"]' || '["locked","lowest"]' }} secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/src/lib/filesystem/src/Flow/Filesystem/Local/NativeLocalFilesystem.php b/src/lib/filesystem/src/Flow/Filesystem/Local/NativeLocalFilesystem.php index b2f303987..650f029a1 100644 --- a/src/lib/filesystem/src/Flow/Filesystem/Local/NativeLocalFilesystem.php +++ b/src/lib/filesystem/src/Flow/Filesystem/Local/NativeLocalFilesystem.php @@ -42,6 +42,7 @@ use function rename; use function rmdir; use function scandir; +use function sort; use function sprintf; use function str_ends_with; use function str_replace; @@ -103,8 +104,15 @@ public function list(Path $path, Filter $pathFilter = new OnlyFiles()): Generato return; } + $filePaths = []; + foreach (new GlobIterator($path->glob()) as $filePath) { - $filePath = type_string()->assert($filePath); + $filePaths[] = type_string()->assert($filePath); + } + + sort($filePaths, SORT_STRING); + + foreach ($filePaths as $filePath) { $status = self::statFor(path_real($filePath, $path->options()), $filePath); if ($pathFilter->accept($status)) { diff --git a/src/lib/filesystem/tests/Flow/Filesystem/Tests/Integration/NativeLocalFilesystemTest.php b/src/lib/filesystem/tests/Flow/Filesystem/Tests/Integration/NativeLocalFilesystemTest.php index f9379f28a..6cc0efec6 100644 --- a/src/lib/filesystem/tests/Flow/Filesystem/Tests/Integration/NativeLocalFilesystemTest.php +++ b/src/lib/filesystem/tests/Flow/Filesystem/Tests/Integration/NativeLocalFilesystemTest.php @@ -27,7 +27,6 @@ use function iterator_to_array; use function mb_substr; use function mkdir; -use function sort; final class NativeLocalFilesystemTest extends NativeLocalFilesystemTestCase { @@ -180,10 +179,40 @@ public function test_list_files_matching_partition_placeholder_pattern(): void $files = iterator_to_array($fs->list(path(__DIR__ . '/var/placeholders/order-year=2024/{order-name}.csv'))); - $basenames = array_map(static fn(FileStatus $status) => $status->path->basename(), $files); - sort($basenames); + static::assertSame( + ['123456-PL.csv', '789-DE.csv'], + array_map(static fn(FileStatus $status) => $status->path->basename(), $files), + ); + } + + public function test_list_matches_are_sorted_by_path(): void + { + $fs = native_local_filesystem(); + $fs->rm(path(__DIR__ . '/var/list_order')); + + foreach (['c', 'a', 'b'] as $directory) { + foreach (['file_02.txt', 'file_01.txt'] as $file) { + $fs->writeTo(path(__DIR__ . '/var/list_order/' . $directory . '/' . $file))->append('data'); + } + } - static::assertSame(['123456-PL.csv', '789-DE.csv'], $basenames); + $expected = [ + path(__DIR__ . '/var/list_order/a/file_01.txt')->uri(), + path(__DIR__ . '/var/list_order/a/file_02.txt')->uri(), + path(__DIR__ . '/var/list_order/b/file_01.txt')->uri(), + path(__DIR__ . '/var/list_order/b/file_02.txt')->uri(), + path(__DIR__ . '/var/list_order/c/file_01.txt')->uri(), + path(__DIR__ . '/var/list_order/c/file_02.txt')->uri(), + ]; + + static::assertSame($expected, array_map( + static fn(FileStatus $status): string => $status->path->uri(), + iterator_to_array($fs->list(path(__DIR__ . '/var/list_order/*/*.txt'), new KeepAll())), + )); + static::assertSame($expected, array_map( + static fn(FileStatus $status): string => $status->path->uri(), + iterator_to_array($fs->list(path(__DIR__ . '/var/list_order/**/*.txt'), new KeepAll())), + )); } public function test_list_rejects_mismatched_scheme(): void @@ -288,7 +317,6 @@ public function test_reading_multi_partitioned_path(): void flow_context(), ), )); - sort($paths); $path1 = path(__DIR__ . '/Fixtures/multi_partitions/date=2022-01-02/country=pl/file.txt'); $path1->partitions(); @@ -305,7 +333,6 @@ public function test_reading_partitioned_folder(): void path(__DIR__ . '/Fixtures/partitioned/**/*.txt'), new KeepAll(), )); - sort($statuses); $uris = array_map(static fn(FileStatus $s): string => $s->path->uri(), $statuses); static::assertSame( @@ -342,7 +369,6 @@ public function test_reading_partitioned_folder_with_pattern(): void path(__DIR__ . '/Fixtures/partitioned/partition_01=*/*.txt'), new KeepAll(), )); - sort($statuses); $uris = array_map(static fn(FileStatus $s): string => $s->path->uri(), $statuses); static::assertSame( @@ -532,7 +558,6 @@ public function test_scan_yields_all_matching_files(): void . '/Fixtures/multi_partitions/**/*.txt'))); $uris = array_map(static fn(FileStatus $s): string => $s->path->uri(), $statuses); - sort($uris); static::assertSame( [