diff --git a/.github/workflows/test-suite-windows.yml b/.github/workflows/test-suite-windows.yml new file mode 100644 index 000000000..39688a742 --- /dev/null +++ b/.github/workflows/test-suite-windows.yml @@ -0,0 +1,88 @@ +# Windows doesn't support Docker out-of-box so we can run mostly unit test +name: Test Suite on Windows + +on: + pull_request: + paths: + - '.github/workflows/**' + - 'src/adapter/**' + - 'src/core/**' + - 'src/lib/**' + - 'tools/**' + - 'examples/**' + - 'composer.lock' + push: + branches: [ 1.x ] + paths-ignore: + - 'CHANGELOG.md' + +# See https://stackoverflow.com/a/72408109 +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + tests: + name: "Tests" + + runs-on: "windows-latest" + + strategy: + fail-fast: false + matrix: + dependencies: + - "locked" + - "lowest" + - "highest" + php-version: + - "8.1" + - "8.2" + - "8.3" + - "8.4" + + steps: + - name: "Checkout" + uses: "actions/checkout@v4" + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + tools: composer:v2 + php-version: "${{ matrix.php-version }}" + ini-values: memory_limit=-1 + extensions: :psr, bcmath, dom, hash, json, mbstring, xml, xmlwriter, xmlreader, zlib, sodium + + - name: "List PHP Extensions" + run: php -m + + - name: "List PHP configuration" + run: php -i + + - name: "Get Composer Cache Directory" + id: composer-cache + run: | + echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: "Cache Composer dependencies" + uses: "actions/cache@v4" + with: + path: "~\\AppData\\Roaming\\composer" + key: "${{ runner.os }}-php-${{ matrix.php-version }}-${{ matrix.dependencies }}-composer-${{ hashFiles('**\\composer.lock') }}" + restore-keys: | + ${{ runner.os }}-php-${{ matrix.php-version }}-${{ matrix.dependencies }}-composer- + + - name: "Install lowest dependencies" + if: ${{ matrix.dependencies == 'lowest' }} + run: "composer update --prefer-lowest --no-interaction --no-progress --no-suggest" + + - name: "Install highest dependencies" + if: ${{ matrix.dependencies == 'highest' }} + run: "composer update --no-interaction --no-progress --no-suggest" + + - name: "Install locked dependencies" + if: ${{ matrix.dependencies == 'locked' }} + run: "composer install --no-interaction --no-progress --no-suggest" + + # Please see note at top why only running unit tests + - name: "Test" + run: "composer test -- --testsuite unit" diff --git a/src/lib/filesystem/src/Flow/Filesystem/Path.php b/src/lib/filesystem/src/Flow/Filesystem/Path.php index b6e25e072..c7414b1b1 100644 --- a/src/lib/filesystem/src/Flow/Filesystem/Path.php +++ b/src/lib/filesystem/src/Flow/Filesystem/Path.php @@ -35,7 +35,7 @@ public function __construct(string $uri, array|Options $options = []) $path = \str_replace($scheme . '://', '', $uri); - if (!\str_starts_with($path, DIRECTORY_SEPARATOR)) { + if (PHP_OS_FAMILY !== 'Windows' && !\str_starts_with($path, DIRECTORY_SEPARATOR)) { $path = DIRECTORY_SEPARATOR . $path; } diff --git a/src/lib/filesystem/tests/Flow/Filesystem/Tests/Unit/PathTest.php b/src/lib/filesystem/tests/Flow/Filesystem/Tests/Unit/PathOnUnixTest.php similarity index 98% rename from src/lib/filesystem/tests/Flow/Filesystem/Tests/Unit/PathTest.php rename to src/lib/filesystem/tests/Flow/Filesystem/Tests/Unit/PathOnUnixTest.php index f867e9952..6036b3e73 100644 --- a/src/lib/filesystem/tests/Flow/Filesystem/Tests/Unit/PathTest.php +++ b/src/lib/filesystem/tests/Flow/Filesystem/Tests/Unit/PathOnUnixTest.php @@ -7,9 +7,11 @@ use function Flow\Filesystem\DSL\{partition, partitions, path, path_real}; use Flow\Filesystem\Partitions; use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\RequiresOperatingSystemFamily; use PHPUnit\Framework\TestCase; -final class PathTest extends TestCase +#[RequiresOperatingSystemFamily('Linux')] +final class PathOnUnixTest extends TestCase { public static function directories() : \Generator { diff --git a/src/lib/filesystem/tests/Flow/Filesystem/Tests/Unit/PathOnWindowsTest.php b/src/lib/filesystem/tests/Flow/Filesystem/Tests/Unit/PathOnWindowsTest.php new file mode 100644 index 000000000..a587ae67b --- /dev/null +++ b/src/lib/filesystem/tests/Flow/Filesystem/Tests/Unit/PathOnWindowsTest.php @@ -0,0 +1,291 @@ + ['some_file.txt', '']; + yield 'some\nested\file.csv' => ['some\nested\file.csv', 'some\nested']; + yield 'flow-file://nested\file\path\file.txt' => ['flow-file://nested\file\path\file.txt', 'nested\file\path']; + } + + /** + * @return \Generator> - string $uri, string $schema, string $parsedUri + */ + public static function paths() : \Generator + { + yield 'file.csv' => ['file.csv', 'file', 'file://file.csv']; + yield 'file://file.csv' => ['file://file.csv', 'file', 'file://file.csv']; + yield '' => ['', 'file', 'file://']; + yield 'absolute\path\to\file.txt' => ['absolute\path\to\file.txt', 'file', 'file://absolute\path\to\file.txt']; + yield 'file://absolute\path\to\file.txt' => ['file://absolute\path\to\file.txt', 'file', 'file://absolute\path\to\file.txt']; + yield 'flow-file://' => ['flow-file://', 'flow-file', 'flow-file://']; + yield 'flow-file://folder\file.csv' => ['flow-file://folder\file.csv', 'flow-file', 'flow-file://folder\file.csv']; + } + + public static function paths_pattern_matching() : \Generator + { + yield ['file.csv', 'file.csv', true]; + yield ['nested\folder\any\file.csv', 'nested\folder\*/file.csv', false]; + yield ['nested\folder\*/file.csv', 'nested\folder\any\file.csv', true]; + yield ['nested\folder\[a]*/file.csv', 'nested\folder\ab\file.csv', true]; + yield ['nested\folder/**/file.csv', 'nested\folder\any\nested\file.csv', true]; + yield ['nested\folder/**/fil?.csv', 'nested\folder\any\nested\file.csv', true]; + } + + public static function paths_with_partitions() : \Generator + { + yield '' => ['', partitions()]; + yield 'file://path\without\partitions\file.csv' => ['file://path\without\partitions\file.csv', partitions()]; + yield 'file://path\country=US\file.csv' => ['file://path\country=US\file.csv', partitions(partition('country', 'US'))]; + yield 'file://path\country=US\region=america\file.csv' => ['file://path\country=US\region=america\file.csv', partitions(partition('country', 'US'), partition('region', 'america'))]; + yield 'file://path\country=*/file.csv' => ['file://path\country=*/file.csv', partitions()]; + } + + public static function paths_with_static_parts() : \Generator + { + yield 'file.csv' => ['file.csv', 'file.csv']; + yield 'nested\folder\*/file.csv' => ['nested\folder', 'nested\folder\*/file.csv']; + yield 'nested\folder\path\{one|two}\file.csv' => ['nested\folder\path', 'nested\folder\path\{one|two}\file.csv']; + yield 'file*.csv' => ['', 'file*.csv']; + yield '{one|two|tree}.csv' => ['', '{one|two|tree}.csv']; + yield 'file.{parquet|csv}' => ['', 'file.{parquet|csv}']; + yield 'flow-file://nested\partition={one,two}\*.csv' => ['flow-file://nested', 'flow-file://nested\partition={one,two}\*.csv']; + yield 'flow-file://nested\partition=[one]\*.csv' => ['flow-file://nested', 'flow-file://nested\partition=[one]\*.csv']; + yield 'nested\partition=[one]\*.csv' => ['file://nested', 'nested\partition=[one]\*.csv']; + } + + protected function setUp() : void + { + if (!\file_exists(__DIR__ . 'var')) { + \mkdir(__DIR__ . 'var'); + } + } + + public function test_add_partitions_to_path_pattern() : void + { + $this->expectExceptionMessage("Can't add partitions to path pattern."); + + (path('path\to\group=*/file.txt'))->addPartitions(partition('group', 'a')); + } + + public function test_add_partitions_to_path_with_extension() : void + { + self::assertEquals( + path('path\to\group=a\file.txt'), + (path('path\to\file.txt'))->addPartitions(partition('group', 'a')) + ); + } + + public function test_add_partitions_to_path_without_extension() : void + { + self::assertEquals( + path('path\to\group=a\folder'), + (path('path\to\folder'))->addPartitions(partition('group', 'a')) + ); + } + + public function test_add_partitions_to_root_path_with_extension() : void + { + self::assertEquals( + path('group=a\file.txt'), + (path('file.txt'))->addPartitions(partition('group', 'a')) + ); + } + + public function test_add_partitions_to_root_path_without_extension() : void + { + self::assertEquals( + path('group=a\folder'), + (path('folder'))->addPartitions(partition('group', 'a')) + ); + } + + #[DataProvider('directories')] + public function test_directories(string $uri, string $dirPath) : void + { + self::assertSame($dirPath, (path($uri))->parentDirectory()->path()); + } + + public function test_extension() : void + { + self::assertSame('php', (path(__FILE__))->extension()); + self::assertFalse((path(__DIR__))->extension()); + } + + public function test_extension_uppercase() : void + { + self::assertSame('php', (path('var\file\code.PhP'))->extension()); + } + + public function test_file_prefix() : void + { + $path = path('flow-file://var\dir\file.csv', []); + + self::assertSame( + 'flow-file://var\dir\._flow_tmp.file.csv', + $path->basenamePrefix('._flow_tmp.')->uri() + ); + self::assertSame('csv', $path->extension()); + } + + public function test_file_prefix_on_directory() : void + { + $path = path('flow-file://var\dir', []); + + self::assertSame( + 'flow-file://var\._flow_tmp.dir', + $path->basenamePrefix('._flow_tmp.')->uri() + ); + self::assertFalse($path->extension()); + } + + public function test_file_prefix_on_root_directory() : void + { + $path = path('flow-file://', []); + + self::assertSame( + 'flow-file://._flow_tmp.', + $path->basenamePrefix('._flow_tmp.')->uri() + ); + self::assertFalse($path->extension()); + } + + #[DataProvider('paths_with_static_parts')] + public function test_finding_static_part_of_the_path(string $staticPart, string $uri) : void + { + self::assertEquals(path($staticPart), (path($uri))->staticPart()); + } + + public function test_local_file() : void + { + self::assertNull((path(__FILE__))->context()->resource()); + } + + #[DataProvider('paths_pattern_matching')] + public function test_matching_pattern_with_path(string $path, string $pattern, bool $result) : void + { + self::assertSame($result, (path($path))->matches(path($pattern))); + } + + public function test_not_matching_items_under_directory_that_matches_pattern() : void + { + $path = path('flow-file://var\file\partition=*'); + + self::assertTrue($path->matches(path('flow-file://var\file\partition=1'))); + self::assertFalse($path->matches(path('flow-file://var\file\partition=1\file.csv'))); + } + + #[DataProvider('paths')] + public function test_parsing_path(string $uri, string $schema, string $parsedUri) : void + { + self::assertEquals($schema, (path($uri))->protocol()->name); + self::assertEquals($parsedUri, (path($uri))->uri()); + } + + #[DataProvider('paths_with_partitions')] + public function test_partitions_in_path(string $uri, Partitions $partitions) : void + { + self::assertEquals($partitions, (path($uri))->partitions()); + } + + public function test_partitions_paths() : void + { + $path = path('var\path\partition_1=A\partition_2=B\file.csv', ['option' => true]); + + self::assertEquals( + [ + path('var\path\partition_1=A', ['option' => true]), + path('var\path\partition_1=A\partition_2=B', ['option' => true]), + ], + $path->partitionsPaths() + ); + } + + public function test_randomization_file_path() : void + { + $path = path('flow-file://var\file\test.csv', []); + + self::assertStringStartsWith( + 'flow-file://var\file\test_', + $path->randomize()->uri() + ); + self::assertStringEndsWith( + '.csv', + $path->randomize()->uri() + ); + } + + public function test_randomization_folder_path() : void + { + $path = path('flow-file://var\file\folder', []); + + self::assertStringStartsWith( + 'flow-file://var\file\folder_', + $path->randomize()->uri() + ); + } + + public function test_real_path_on_custom_schema() : void + { + $path = path_real('azure-blob://var\dir\file.php'); + + self::assertSame('azure-blob://var\dir\file.php', $path->uri()); + } + + public function test_set_extension() : void + { + $path = path('flow-file://var\dir\file.csv', []); + + self::assertSame( + 'flow-file://var\dir\file.parquet', + $path->setExtension('parquet')->uri() + ); + } + + public function test_set_extension_on_directory() : void + { + $path = path('flow-file://var\dir\\', []); + + self::assertSame( + 'flow-file://var\dir.parquet', + $path->setExtension('parquet')->uri() + ); + } + + public function test_set_extension_on_file_without_extension() : void + { + $path = path('flow-file://var\dir\file', []); + + self::assertSame( + 'flow-file://var\dir\file.parquet', + $path->setExtension('parquet')->uri() + ); + } + + public function test_suffix() : void + { + $path = path('flow-file://var\dir', []); + + self::assertSame( + 'flow-file://var\dir\test.csv', + $path->suffix('test.csv')->uri() + ); + + self::assertSame( + 'flow-file://var\dir\test.csv', + $path->suffix('test.csv')->uri() + ); + } +}