forked from brandonsavage/Upload
-
Notifications
You must be signed in to change notification settings - Fork 3
288 lines (248 loc) · 13 KB
/
Copy pathphpunit.yml
File metadata and controls
288 lines (248 loc) · 13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
name: PHPUnit
on:
push:
branches:
- main
pull_request:
# The workflows only read the repository; nothing needs a write-capable GITHUB_TOKEN.
permissions:
contents: read
# Cancels all previous workflow runs for pull requests that have not completed.
concurrency:
# The concurrency group contains the workflow name and the branch name for pull requests
# or the commit hash for any other events.
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
cancel-in-progress: true
jobs:
run:
name: ${{ matrix.php }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ ubuntu-latest ]
php: [ '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5' ]
include:
- php: '7.3'
os: ubuntu-latest
report: true
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.0.0
- name: Install PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2.35.5
with:
php-version: ${{ matrix.php }}
- name: Log debug information
run: |
git --version
php --version
php -i
composer --version
# This date is used to ensure that the Composer cache is refreshed at least once every week.
# http://man7.org/linux/man-pages/man1/date.1.html
- name: "Get last Monday's date"
id: get-date
run: echo "date=$(/bin/date -u --date='last Mon' "+%F")" >> "$GITHUB_OUTPUT"
- 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@caa296126883cff596d87d8935842f9db880ef25 # v5.0.4
env:
cache-name: cache-composer-dependencies
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-php-${{ matrix.php }}-date-${{ steps.get-date.outputs.date }}-composer-${{ hashFiles('composer.json') }}
- name: Install Composer dependencies
run: composer install --no-scripts
- name: Run PHPUnit tests
run: vendor/bin/phpunit
- name: Run (xDebug) tests
if: ${{ matrix.report }}
run: vendor/bin/phpunit --verbose --coverage-clover=tmp/coverage/report-xml/php-coverage1.xml
# Replaces `bash <(curl -s https://codecov.io/bash)` — an unpinned remote script piped
# into bash, which was compromised in April 2021 and has since been sunset by Codecov.
- name: Upload coverage to Codecov
if: ${{ matrix.report }}
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.1
with:
files: tmp/coverage/report-xml/php-coverage1.xml
token: ${{ secrets.CODECOV_TOKEN }}
# Reserved device names, `:` naming an NTFS alternate data stream, trailing dots and spaces
# resolving away, `DIRECTORY_SEPARATOR`, `stat()` reporting `ino` as 0 — Windows behaviour
# this library reasons about and had never executed against. It found two bugs on its first
# outing, both on the default `$overwrite = false` path and neither reachable from Linux, so
# it is a gate.
#
# `@group posix` marks what cannot run there: `symlink()` needs a privilege an uploading
# process should not hold, `chmod()` is close to a no-op, and `umask()` means nothing.
# Excluded by group rather than skipped, so `cross-file-system` keeps the single
# `markTestSkipped()` its `--fail-on-skipped` guard depends on.
windows:
name: ${{ matrix.php }} on windows-latest
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
# The ends of the supported range. The middle versions differ from these in nothing
# this job exercises, and a Windows runner costs several times a Linux one.
php: [ '7.3', '8.5' ]
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.0.0
- name: Install PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2.35.5
with:
php-version: ${{ matrix.php }}
# Named because the Windows builds enable neither by default, where the Linux ones
# ship both: `ext-fileinfo` is this library's only requirement and PHPUnit declares
# `ext-mbstring`, so `composer install` cannot resolve without them.
extensions: fileinfo, mbstring
- name: Install Composer dependencies
run: composer install --no-scripts
- name: Run the test suite
run: vendor/bin/phpunit --exclude-group posix
# `ext-mbstring` is `suggest`, not `require`, and every job above runs with it loaded, so an
# unguarded `mb_*` call fatals on a supported install without any of them noticing.
#
# Two jobs, because two constraints collide. PHPUnit declares `ext-mbstring` and will not
# start without it. And `disable_functions`, which stands in for it since `function_exists()`
# is the library's only detection, cannot host the polyfill before PHP 8: a disabled function
# keeps its name reserved there, so redeclaring it fatals even though `function_exists()`
# reports false.
mbstring:
name: ${{ matrix.label }} on PHP ${{ matrix.php }}
runs-on: ubuntu-latest
env:
# `Filename`'s whole mbstring surface, so removing these is what "no mbstring" means here
MB_DISABLED: mb_strcut,mb_detect_encoding,mb_check_encoding,mb_convert_encoding,mb_substitute_character
strategy:
fail-fast: false
matrix:
# `include` alone, so each entry is one job rather than a dimension to multiply out
include:
# Only the valid-UTF-8 guarantee is given up here, which is what `@group mbstring`
# marks. Excluded rather than skipped: the suite keeps exactly one
# `markTestSkipped()` so `cross-file-system` can prove it ran with --fail-on-skipped.
- { php: '7.3', label: No mbstring, polyfill: false, phpunit: --exclude-group mbstring }
- { php: '8.5', label: No mbstring, polyfill: false, phpunit: --exclude-group mbstring }
# No --exclude-group: the README's claim is that the polyfill is equivalent, so the
# suite has to pass exactly as it does with the extension. 8.5 only, per the
# redeclare note above; `Extension absent` carries the claim on 7.3.
- { php: '8.5', label: mbstring polyfill, polyfill: true, phpunit: '' }
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.0.0
- name: Install PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2.35.5
with:
php-version: ${{ matrix.php }}
- name: Install Composer dependencies
run: composer install --no-scripts
- name: Install the mbstring polyfill
if: ${{ matrix.polyfill }}
run: composer require --no-scripts symfony/polyfill-mbstring
# Fail here rather than three steps later with a green suite that proved nothing
- name: Assert the library cannot see mbstring
run: |
php -d disable_functions=$MB_DISABLED -r 'foreach (["mb_strcut", "mb_detect_encoding", "mb_check_encoding", "mb_convert_encoding", "mb_substitute_character"] as $f) { if (function_exists($f)) { fwrite(STDERR, "$f is still callable\n"); exit(1); } }'
# The polyfill defines what `disable_functions` took away, and does not ship
# `mb_strcut()`. Asserting both is what proves this is the polyfill answering.
- name: Assert the polyfill is what is answering
if: ${{ matrix.polyfill }}
run: |
php -d disable_functions=$MB_DISABLED -r 'require "vendor/autoload.php";
if (!function_exists("mb_check_encoding")) { fwrite(STDERR, "polyfill absent\n"); exit(1); }
if (function_exists("mb_strcut")) { fwrite(STDERR, "mb_strcut came back; the gate this job exists for is gone\n"); exit(1); }'
- name: Run the test suite
run: php -d disable_functions=$MB_DISABLED vendor/bin/phpunit ${{ matrix.phpunit }}
# The extension genuinely absent, which PHPUnit cannot be present for. No test framework, just
# the library doing what fatalled. This is the job that would have caught it.
without-mbstring-extension:
name: Extension absent${{ matrix.polyfill && ' with polyfill' || '' }} on PHP ${{ matrix.php }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: [ '7.3', '8.5' ]
polyfill: [ false, true ]
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.0.0
- name: Install PHP without mbstring
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2.35.5
with:
php-version: ${{ matrix.php }}
extensions: :mbstring
- name: Assert mbstring really is gone
run: php -r 'if (extension_loaded("mbstring")) { fwrite(STDERR, "mbstring is still loaded\n"); exit(1); }'
# PHPUnit declares ext-mbstring, so it installs only under the override and cannot run.
# The library needs nothing but ext-fileinfo, which is the point.
- name: Install Composer dependencies
run: composer install --no-scripts --ignore-platform-req=ext-mbstring
- name: Install the mbstring polyfill
if: ${{ matrix.polyfill }}
run: composer require --no-scripts --ignore-platform-req=ext-mbstring symfony/polyfill-mbstring
# Only the UTF-8 repair separates the two configurations, so it is asserted both ways.
# As hex: the un-repaired value is not valid UTF-8, so json_encode() returns false for it,
# which is the bug it causes.
- name: Sanitize with the extension absent
env:
EXPECT_REPAIR: ${{ matrix.polyfill }}
run: |
php -r 'require "vendor/autoload.php";
$sanitize = ["GravityPdf\Upload\Filename", "sanitizeForDisplay"];
$checks = [
"a message" => ["a b", $sanitize("a\nb")],
"a filename" => ["re-port.txt", GravityPdf\Upload\Filename::sanitizeNameWithExtension("re\nport.txt")],
];
foreach ($checks as $what => list($expected, $actual)) {
if ($expected !== $actual) {
fwrite(STDERR, sprintf("%s: expected %s, got %s\n", $what, $expected, $actual));
exit(1);
}
}
$repaired = getenv("EXPECT_REPAIR") === "true";
$expected = $repaired ? "6261642862797465" : "626164c32862797465";
$actual = bin2hex($sanitize("bad\xC3\x28byte"));
if ($expected !== $actual) {
fwrite(STDERR, sprintf("UTF-8 repair (polyfill=%s): expected %s, got %s\n", var_export($repaired, true), $expected, $actual));
exit(1);
}
echo "sanitized without the mbstring extension\n";'
# `moveFile()`'s job is to carry a file across a file-system boundary, which is the
# container layout `FileSystem::acceptFilesNotUploadedByPhp()` exists for. The matrix job
# above runs that test only if it finds a second file system for itself — `/dev/shm` is one
# on the current runner image, but that is the image's choice and not a promise. This mounts
# one explicitly, so the coverage does not quietly disappear the day the image changes.
cross-file-system:
name: Cross file system on PHP ${{ matrix.php }}
runs-on: ubuntu-latest
strategy:
matrix:
# The ends of the supported range: this exercises PHP's own rename() across a device
# boundary, which is C in php-src rather than anything this library controls.
php: [ '7.3', '8.5' ]
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.0.0
- name: Install PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2.35.5
with:
php-version: ${{ matrix.php }}
- name: Install Composer dependencies
run: composer install --no-scripts
- name: Mount a tmpfs to upload from
run: |
sudo mkdir -p /mnt/upload-tmp
sudo mount -t tmpfs -o size=16m,mode=1777 tmpfs /mnt/upload-tmp
# Fail here rather than in a test if the mount did not land on its own device
test "$(stat -c %d /mnt/upload-tmp)" != "$(stat -c %d /tmp)"
# The whole suite, not a --filter: a filter that matches nothing exits 0 with "No tests
# executed!", so it needed a second guard to prove it had run, and a rename was enough to
# cause exactly that. --fail-on-skipped is the only guard left, and the cross-file-system
# test is the suite's only skip, so it cannot pass here having tested nothing.
- name: Run the test suite against two file systems
env:
UPLOAD_TEST_OTHER_FS: /mnt/upload-tmp
run: vendor/bin/phpunit --fail-on-skipped