diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 308fdfb..7caf4df 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -30,11 +30,13 @@ jobs: # To report GitHub Actions status checks GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} LINTER_RULES_PATH: 'tools/linters' + LOG_LEVEL: NOTICE VALIDATE_ALL_CODEBASE: true + VALIDATE_CSS: true + VALIDATE_JAVASCRIPT_ES: true VALIDATE_JSON: true VALIDATE_PHP_BUILTIN: true VALIDATE_YAML: true - VALIDATE_XML: true VALIDATE_GITHUB_ACTIONS: true quality: @@ -74,13 +76,13 @@ jobs: - name: Install Composer dependencies run: composer install --no-progress --prefer-dist --optimize-autoloader - #- name: Check code for hard dependencies missing in composer.json - # run: composer-require-checker check composer.json + - name: Check code for hard dependencies missing in composer.json + run: composer-require-checker check --config-file tools/composer-require-checker.json composer.json - name: Check code for unused dependencies in composer.json run: | composer-unused \ - --excludePackage=slevomat/coding-standard + --excludePackage=slevomat/coding-standard \ --excludePackage=symfony/phpunit-bridge - name: PHP Code Sniffer @@ -141,3 +143,150 @@ jobs: - name: Security check for updated dependencies run: composer audit + + unit-tests-linux: + name: "Unit tests, PHP ${{ matrix.php-versions }}, ${{ matrix.operating-system }}" + runs-on: ${{ matrix.operating-system }} + needs: [linter, quality, security] + strategy: + fail-fast: false + matrix: + operating-system: [ubuntu-latest] + php-versions: ['8.1', '8.2', '8.3'] + + steps: + - name: Setup PHP, with composer and extensions + # https://github.com/shivammathur/setup-php + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + extensions: ctype, date, dom, fileinfo, filter, hash, json, mbstring, mysql, openssl, pcre,\ + pdo, pdo_sqlite, posix, soap, spl, xdebug, xml + tools: composer + ini-values: error_reporting=E_ALL, pcov.directory=. + coverage: pcov + + - name: Setup problem matchers for PHP + run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" + + - name: Setup problem matchers for PHPUnit + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: Set git to use LF + run: | + git config --global core.autocrlf false + git config --global core.eol lf + + - uses: actions/checkout@v4 + + - name: Get composer cache directory + run: echo COMPOSER_CACHE="$(composer config cache-files-dir)" >> "$GITHUB_ENV" + + - name: Cache composer dependencies + uses: actions/cache@v4 + with: + path: $COMPOSER_CACHE + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install Composer dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Run unit tests with coverage + if: ${{ matrix.php-versions == '8.3' }} + run: vendor/bin/phpunit + + - name: Run unit tests (no coverage) + if: ${{ matrix.php-versions != '8.3' }} + run: vendor/bin/phpunit --no-coverage + + - name: Save coverage data + if: ${{ matrix.php-versions == '8.3' }} + uses: actions/upload-artifact@v4 + with: + name: coverage-data + path: ${{ github.workspace }}/build + + unit-tests-windows: + name: "Unit tests, PHP ${{ matrix.php-versions }}, ${{ matrix.operating-system }}" + runs-on: ${{ matrix.operating-system }} + needs: [linter, quality, security] + strategy: + fail-fast: true + matrix: + operating-system: [windows-latest] + php-versions: ['8.1', '8.2', '8.3'] + + steps: + - name: Setup PHP, with composer and extensions + # https://github.com/shivammathur/setup-php + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + extensions: ctype, dom, date, fileinfo, filter, hash, json, mbstring, mysql, openssl, pcre, \ + pdo, pdo_sqlite, posix, soap, spl, xdebug, xml + tools: composer + ini-values: error_reporting=E_ALL + coverage: none + + - name: Setup problem matchers for PHP + run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" + + - name: Setup problem matchers for PHPUnit + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: Set git to use LF + run: | + git config --global core.autocrlf false + git config --global core.eol lf + + - uses: actions/checkout@v4 + + - name: Get composer cache directory + run: echo COMPOSER_CACHE="$(composer config cache-files-dir)" >> "$env:GITHUB_ENV" + + - name: Cache composer dependencies + uses: actions/cache@v4 + with: + path: $COMPOSER_CACHE + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install Composer dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader --ignore-platform-req=ext-posix + + - name: Run unit tests + run: vendor/bin/phpunit --no-coverage + + coverage: + name: Code coverage + runs-on: [ubuntu-latest] + needs: [unit-tests-linux] + steps: + - uses: actions/checkout@v4 + + - uses: actions/download-artifact@v4 + with: + name: coverage-data + path: ${{ github.workspace }}/build + + - name: Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: true + verbose: true + + cleanup: + name: Cleanup artifacts + needs: [unit-tests-linux, coverage] + runs-on: [ubuntu-latest] + if: | + always() && + needs.coverage.result == 'success' || + (needs.unit-tests-linux == 'success' && needs.coverage == 'skipped') + + steps: + - uses: geekyeggo/delete-artifact@v5 + with: + name: coverage-data diff --git a/composer.json b/composer.json index d4231c4..6efe178 100644 --- a/composer.json +++ b/composer.json @@ -15,16 +15,20 @@ "SimpleSAML\\TestUtils\\": "lib/" } }, - "require": { - "phpunit/phpunit": "^10.0 || ^11.0", - "slevomat/coding-standard": "^8.15", - "symfony/phpunit-bridge": "^6.4 || ^7.0" + "autoload-dev": { + "psr-4": { + "SimpleSAML\\Test\\TestUtils\\": "tests/src/" + } }, - "require-dev": { + "require": { "php": "^8.1", "ext-curl": "*", - "simplesamlphp/simplesamlphp": "^2.2.0" + "phpunit/phpunit": "^10.0 || ^11.0", + "psr/log": "^2.0 || ^3.0", + "simplesamlphp/simplesamlphp": "^2.2.0", + "slevomat/coding-standard": "^8.15", + "symfony/phpunit-bridge": "^6.4 || ^7.0" }, "support": { "issues": "https://github.com/simplesamlphp/simplesamlphp-test-framework/issues", diff --git a/lib/InMemoryStore.php b/lib/InMemoryStore.php new file mode 100644 index 0000000..05fec37 --- /dev/null +++ b/lib/InMemoryStore.php @@ -0,0 +1,81 @@ +delete($type, $key); + return null; + } + return $item['value']; + } + return null; + } + + + /** + * Save a value to the data store. + * + * @param string $type The data type. + * @param string $key The key. + * @param mixed $value The value. + * @param int|null $expire The expiration time (unix timestamp), or null if it never expires. + */ + public function set(string $type, string $key, mixed $value, ?int $expire = null): void + { + self::$store[$key] = [ + 'type' => $type, + 'value' => $value, + 'expire' => $expire + ]; + } + + + /** + * Delete a value from the data store. + * + * @param string $type The data type. + * @param string $key The key. + */ + public function delete(string $type, string $key): void + { + unset(self::$store[$key]); + } + + + /** + * Clear any cached internal state. + */ + public static function clearInternalState(): void + { + self::$store = []; + } +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..6971499 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + ./tests + + + + + + ./lib + ./src + + + diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..28c2413 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,6 @@ + 'SimpleSAML\TestUtils\InMemoryStore', + ]; + Configuration::loadFromArray($config, '[ARRAY]', 'simplesaml'); + + // when: getting the store + $store = StoreFactory::getInstance('SimpleSAML\TestUtils\InMemoryStore'); + + // then: will give us the right type of store + $this->assertInstanceOf(InMemoryStore::class, $store); + + // and: we can store stuff + $this->assertNull($store->get('string', 'key'), 'Key does not exist yet'); + $store->set('string', 'key', 'value'); + $this->assertEquals('value', $store->get('string', 'key')); + $store->delete('string', 'key'); + $this->assertNull($store->get('string', 'key'), 'Key was removed'); + } + + + public function testExpiration(): void + { + $store = new InMemoryStore(); + $store->set('string', 'key', 'value', time() + 1); + $this->assertEquals('value', $store->get('string', 'key')); + $this->assertEquals('value', $store->get('string', 'key')); + sleep(2); + $this->assertNull($store->get('string', 'key')); + } +} diff --git a/tools/composer-require-checker.json b/tools/composer-require-checker.json new file mode 100644 index 0000000..eed71aa --- /dev/null +++ b/tools/composer-require-checker.json @@ -0,0 +1,4 @@ +{ + "symbol-whitelist": [ + ] +}