diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml index f4a2874..887681a 100644 --- a/.github/workflows/acceptance.yml +++ b/.github/workflows/acceptance.yml @@ -47,15 +47,17 @@ jobs: - name: Run acceptance tests run: php vendor/bin/testo --suite=Acceptance + # rr against the real GitHub: about 6 API requests per job with a cold DLoad cache, 5 with a warm one live: - name: Live GitHub, ${{ matrix.os }} - if: github.event_name == 'workflow_dispatch' + name: Live GitHub runs-on: ${{ matrix.os }} timeout-minutes: 15 strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest] + env: + DLOAD_CACHE_DIR: ${{ github.workspace }}/runtime/dload-cache steps: - name: Checkout uses: actions/checkout@v7 @@ -74,8 +76,30 @@ jobs: with: dependency-versions: highest + # Release metadata only, no binaries or tokens. A cache key is never overwritten, + # so every run saves its own registry and the next run starts from the newest one. + - name: Restore DLoad version registry + uses: actions/cache@v6 + with: + path: runtime/dload-cache + key: dload-registry-${{ runner.os }}-${{ github.run_id }} + restore-keys: dload-registry-${{ runner.os }}- + + - name: GitHub API usage before + run: gh api rate_limit --jq '.resources.core | "used \(.used) of \(.limit)"' + shell: bash + env: + GH_TOKEN: ${{ github.token }} + - name: Run live tests run: php vendor/bin/testo --suite=Live env: RR_CLI_LIVE_TESTS: '1' GITHUB_TOKEN: ${{ github.token }} + + - name: GitHub API usage after + if: always() + run: gh api rate_limit --jq '.resources.core | "used \(.used) of \(.limit)"' + shell: bash + env: + GH_TOKEN: ${{ github.token }} diff --git a/tests/Live/GitHubTest.php b/tests/Live/GitHubTest.php index 85569b6..4f91f18 100644 --- a/tests/Live/GitHubTest.php +++ b/tests/Live/GitHubTest.php @@ -13,11 +13,20 @@ use Testo\Test; /** - * `rr get` against the real GitHub. The suite is registered only when RR_CLI_LIVE_TESTS is set. + * `rr` against the real GitHub. The suite is registered only when RR_CLI_LIVE_TESTS is set. + * + * New RoadRunner releases must not break these tests: they pin a release line that is complete, + * or check what holds for any release. */ #[Test] final class GitHubTest { + /** + * Passed through to `rr`: a token lifts the rate limit, and a shared DLoad cache spares the requests + * for release pages already seen. + */ + private const PASSED_ENV = ['GITHUB_TOKEN', 'DLOAD_CACHE_DIR']; + private Workdir $dir; #[BeforeTest] @@ -36,15 +45,43 @@ public function cleanup(): void #[DataSet(['windows', 'amd64', 'rr.exe'])] public function downloadsRealBinary(string $os, string $arch, string $binary): void { - $rr = new Rr($this->dir->path, null); - $token = \getenv('GITHUB_TOKEN'); - if (\is_string($token) && $token !== '') { - $rr = $rr->withEnv('GITHUB_TOKEN', $token); - } - - $result = $rr->run(['get', '--filter=2025.*', "--os=$os", "--arch=$arch", '--no-config']); + $result = $this->rr()->run(['get', '--filter=2025.*', "--os=$os", "--arch=$arch", '--no-config']); Assert::same($result->exitCode, 0, (string) $result); Assert::int((int) \filesize($this->dir->file($binary)))->greaterThan(1_000_000); } + + public function downloadsSemverReleaseByDefault(): void + { + $result = $this->rr()->run(['get', '--os=linux', '--arch=amd64', '--no-config']); + + Assert::same($result->exitCode, 0, (string) $result); + Assert::int((int) \filesize($this->dir->file('rr')))->greaterThan(1_000_000); + Assert::same(\preg_match('/\brr \(v?(\d+)\.\d+\.\d+[^)]*\) has been installed/', $result->output(), $match), 1, (string) $result); + // Calendar versions (2023.x-2025.x) are older than 3.x and must lose to it + Assert::int((int) $match[1])->greaterThanOrEqual(3)->lessThan(2000); + } + + public function listsReleasesWithCompatibility(): void + { + $result = $this->rr()->run(['versions', '--os=linux', '--arch=amd64']); + + Assert::same($result->exitCode, 0, (string) $result); + Assert::string($result->stdout) + ->matchesRegex('/^\s*3\.\d+\.\d+\s+stable\s+✓\s+\(\d+\)\s+✓\s*$/mu') + ->matchesRegex('/^\s*2025\.1\.15\s+stable\s+.*\(reason: incompatible version\)/mu'); + } + + private function rr(): Rr + { + $rr = new Rr($this->dir->path, null); + foreach (self::PASSED_ENV as $name) { + $value = \getenv($name); + if (\is_string($value) && $value !== '') { + $rr = $rr->withEnv($name, $value); + } + } + + return $rr; + } }