• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

orchestral / testbench-core / 17538372481

08 Sep 2025 03:03AM UTC coverage: 92.096% (-0.4%) from 92.525%
17538372481

push

github

crynobone
Merge branch '10.x' into 11.x

37 of 45 new or added lines in 5 files covered. (82.22%)

2 existing lines in 2 files now uncovered.

1538 of 1670 relevant lines covered (92.1%)

75.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.53
/src/functions.php
1
<?php
2

3
namespace Orchestra\Testbench;
4

5
use Closure;
6
use Illuminate\Contracts\Console\Kernel as ConsoleKernel;
7
use Illuminate\Contracts\Foundation\Application as ApplicationContract;
8
use Illuminate\Filesystem\Filesystem;
9
use Illuminate\Foundation\Application;
10
use Illuminate\Routing\Router;
11
use Illuminate\Support\Arr;
12
use Illuminate\Support\Collection;
13
use Illuminate\Support\ProcessUtils;
14
use Illuminate\Support\Str;
15
use Illuminate\Testing\PendingCommand;
16
use InvalidArgumentException;
17
use Orchestra\Sidekick;
18
use PHPUnit\Framework\TestCase as PHPUnitTestCase;
19
use PHPUnit\Runner\ShutdownHandler;
20

21
/**
22
 * Create Laravel application instance.
23
 *
24
 * @api
25
 *
26
 * @param  string|null  $basePath
27
 * @param  (callable(\Illuminate\Foundation\Application):(void))|null  $resolvingCallback
28
 * @param  array{extra?: array{providers?: array, dont-discover?: array, env?: array}, load_environment_variables?: bool, enabled_package_discoveries?: bool}  $options
29
 * @param  \Orchestra\Testbench\Foundation\Config|null  $config
30
 * @return \Orchestra\Testbench\Foundation\Application
31
 */
32
function container(
33
    ?string $basePath = null,
34
    ?callable $resolvingCallback = null,
35
    array $options = [],
36
    ?Foundation\Config $config = null
37
): Foundation\Application {
38
    if ($config instanceof Foundation\Config) {
3✔
39
        return Foundation\Application::makeFromConfig($config, $resolvingCallback, $options);
×
40
    }
41

42
    return Foundation\Application::make($basePath, $resolvingCallback, $options);
3✔
43
}
44

45
/**
46
 * Run artisan command.
47
 *
48
 * @api
49
 *
50
 * @param  \Orchestra\Testbench\Contracts\TestCase|\Illuminate\Contracts\Foundation\Application  $context
51
 * @param  string  $command
52
 * @param  array<string, mixed>  $parameters
53
 * @return int
54
 */
55
function artisan(Contracts\TestCase|ApplicationContract $context, string $command, array $parameters = []): int
56
{
57
    if ($context instanceof ApplicationContract) {
15✔
58
        return $context->make(ConsoleKernel::class)->call($command, $parameters);
1✔
59
    }
60

61
    $command = $context->artisan($command, $parameters);
15✔
62

63
    return $command instanceof PendingCommand ? $command->run() : $command;
15✔
64
}
65

66
/**
67
 * Emit an exit event within a test.
68
 *
69
 * @param  \PHPUnit\Framework\TestCase|object|null  $testCase
70
 * @param  string|int  $status
71
 * @return never
72
 */
73
function bail(?object $testCase, string|int $status = 0): never
74
{
NEW
75
    if ($testCase instanceof PHPUnitTestCase && Sidekick\phpunit_version_compare('12.3.5', '>=')) {
×
NEW
76
        ShutdownHandler::resetMessage();
×
77
    }
78

NEW
79
    exit($status);
×
80
}
81

82
/**
83
 * Emit an exit event within a test.
84
 *
85
 * @param  \PHPUnit\Framework\TestCase|object|null  $testCase
86
 * @param  string|int  $status
87
 * @return never
88
 */
89
function terminate(?object $testCase, string|int $status = 0): never
90
{
NEW
91
    bail($testCase, $status);
×
92
}
93

94
/**
95
 * Run remote action using Testbench CLI.
96
 *
97
 * @api
98
 *
99
 * @param  (\Closure():(mixed))|array<int, string>|string  $command
100
 * @param  array<string, mixed>|string  $env
101
 * @param  bool|null  $tty
102
 * @return \Orchestra\Testbench\Foundation\Process\ProcessDecorator
103
 */
104
function remote(Closure|array|string $command, array|string $env = [], ?bool $tty = null): Foundation\Process\ProcessDecorator
105
{
106
    $remote = new Foundation\Process\RemoteCommand(
12✔
107
        package_path(), $env, $tty
12✔
108
    );
12✔
109

110
    $binary = \defined('TESTBENCH_DUSK') ? 'testbench-dusk' : 'testbench';
12✔
111

112
    $commander = is_file($vendorBinary = package_path('vendor', 'bin', $binary))
12✔
113
        ? $vendorBinary
×
114
        : $binary;
12✔
115

116
    return $remote->handle($commander, $command);
12✔
117
}
118

119
/**
120
 * Register after resolving callback.
121
 *
122
 * @api
123
 *
124
 * @param  \Illuminate\Contracts\Foundation\Application  $app
125
 * @param  string  $name
126
 * @param  (\Closure(object, \Illuminate\Contracts\Foundation\Application):(mixed))|null  $callback
127
 * @return void
128
 */
129
function after_resolving(ApplicationContract $app, string $name, ?Closure $callback = null): void
130
{
131
    $app->afterResolving($name, $callback);
192✔
132

133
    if ($app->resolved($name)) {
192✔
134
        value($callback, $app->make($name), $app);
1✔
135
    }
136
}
137

138
/**
139
 * Load migration paths.
140
 *
141
 * @api
142
 *
143
 * @param  \Illuminate\Contracts\Foundation\Application  $app
144
 * @param  array<int, string>|string  $paths
145
 * @return void
146
 */
147
function load_migration_paths(ApplicationContract $app, array|string $paths): void
148
{
149
    after_resolving($app, 'migrator', static function ($migrator) use ($paths) {
51✔
150
        foreach (Arr::wrap($paths) as $path) {
15✔
151
            /** @var \Illuminate\Database\Migrations\Migrator $migrator */
152
            $migrator->path($path);
15✔
153
        }
154
    });
51✔
155
}
156

157
/**
158
 * Get defined environment variables.
159
 *
160
 * @api
161
 *
162
 * @return array<string, mixed>
163
 */
164
function defined_environment_variables(): array
165
{
166
    return (new Collection(array_merge($_SERVER, $_ENV)))
12✔
167
        ->keys()
12✔
168
        ->mapWithKeys(static fn (string $key) => [$key => Sidekick\Env::forward($key)])
12✔
169
        ->unless(
12✔
170
            Sidekick\Env::has('TESTBENCH_WORKING_PATH'), static fn ($env) => $env->put('TESTBENCH_WORKING_PATH', package_path())
12✔
171
        )->all();
12✔
172
}
173

174
/**
175
 * Get default environment variables.
176
 *
177
 * @api
178
 *
179
 * @param  iterable<string, mixed>  $variables
180
 * @return array<int, string>
181
 */
182
function parse_environment_variables($variables): array
183
{
184
    return (new Collection($variables))
4✔
185
        ->transform(static function ($value, $key) {
4✔
186
            if (\is_bool($value) || \in_array($value, ['true', 'false'])) {
4✔
187
                $value = \in_array($value, [true, 'true']) ? '(true)' : '(false)';
4✔
188
            } elseif (\is_null($value) || \in_array($value, ['null'])) {
1✔
189
                $value = '(null)';
1✔
190
            } else {
191
                $value = $key === 'APP_DEBUG' ? \sprintf('(%s)', Str::of($value)->ltrim('(')->rtrim(')')) : "'{$value}'";
1✔
192
            }
193

194
            return "{$key}={$value}";
4✔
195
        })->values()->all();
4✔
196
}
197

198
/**
199
 * Refresh router lookups.
200
 *
201
 * @api
202
 *
203
 * @param  \Illuminate\Routing\Router  $router
204
 * @return void
205
 */
206
function refresh_router_lookups(Router $router): void
207
{
208
    $router->getRoutes()->refreshNameLookups();
192✔
209
}
210

211
/**
212
 * Transform realpath to alias path.
213
 *
214
 * @api
215
 *
216
 * @param  string  $path
217
 * @param  string|null  $workingPath
218
 * @return string
219
 */
220
function transform_realpath_to_relative(string $path, ?string $workingPath = null, string $prefix = ''): string
221
{
222
    $separator = DIRECTORY_SEPARATOR;
13✔
223

224
    if (! \is_null($workingPath)) {
13✔
225
        return str_replace(rtrim($workingPath, $separator).$separator, $prefix.$separator, $path);
1✔
226
    }
227

228
    $laravelPath = base_path();
12✔
229
    $workbenchPath = workbench_path();
12✔
230
    $packagePath = package_path();
12✔
231

232
    return match (true) {
233
        str_starts_with($path, $laravelPath) => str_replace($laravelPath.$separator, '@laravel'.$separator, $path),
12✔
234
        str_starts_with($path, $workbenchPath) => str_replace($workbenchPath.$separator, '@workbench'.$separator, $path),
8✔
235
        str_starts_with($path, $packagePath) => str_replace($packagePath.$separator, '.'.$separator, $path),
8✔
236
        ! empty($prefix) => implode($separator, [$prefix, ltrim($path, $separator)]),
8✔
237
        default => $path,
12✔
238
    };
239
}
240

241
/**
242
 * Get the default skeleton path.
243
 *
244
 * @api
245
 *
246
 * @no-named-arguments
247
 *
248
 * @param  array<int, string|null>|string  ...$path
249
 * @return ($path is '' ? string : string|false)
250
 */
251
function default_skeleton_path(array|string $path = ''): string|false
252
{
253
    return realpath(
189✔
254
        Sidekick\join_paths(__DIR__, '..', 'laravel', ...Arr::wrap(\func_num_args() > 1 ? \func_get_args() : $path))
189✔
255
    );
189✔
256
}
257

258
/**
259
 * Determine if application is bootstrapped using Testbench's default skeleton.
260
 *
261
 * @param  string|null  $basePath
262
 * @return bool
263
 */
264
function uses_default_skeleton(?string $basePath = null): bool
265
{
266
    $basePath ??= base_path();
192✔
267

268
    return realpath(Sidekick\join_paths($basePath, 'bootstrap', '.testbench-default-skeleton')) !== false;
192✔
269
}
270

271
/**
272
 * Get the migration path by type.
273
 *
274
 * @api
275
 *
276
 * @param  string|null  $type
277
 * @return string
278
 *
279
 * @throws \InvalidArgumentException
280
 */
281
function default_migration_path(?string $type = null): string
282
{
283
    $path = realpath(
58✔
284
        \is_null($type) ? base_path('migrations') : base_path(Sidekick\join_paths('migrations', $type))
58✔
285
    );
58✔
286

287
    if ($path === false) {
58✔
288
        throw new InvalidArgumentException(\sprintf('Unable to resolve migration path for type [%s]', $type ?? 'laravel'));
×
289
    }
290

291
    return $path;
58✔
292
}
293

294
/**
295
 * Get the path to the package folder.
296
 *
297
 * @api
298
 *
299
 * @no-named-arguments
300
 *
301
 * @param  array<int, string|null>|string  ...$path
302
 * @return string
303
 */
304
function package_path(array|string $path = ''): string
305
{
306
    $argumentCount = \func_num_args();
93✔
307

308
    $workingPath = \defined('TESTBENCH_WORKING_PATH')
93✔
309
        ? TESTBENCH_WORKING_PATH
54✔
310
        : Sidekick\Env::get('TESTBENCH_WORKING_PATH', getcwd());
39✔
311

312
    if ($argumentCount === 1 && \is_string($path) && str_starts_with($path, './')) {
93✔
313
        return Sidekick\transform_relative_path($path, $workingPath);
7✔
314
    }
315

316
    $path = Sidekick\join_paths(...Arr::wrap($argumentCount > 1 ? \func_get_args() : $path));
93✔
317

318
    return str_starts_with($path, './')
93✔
319
        ? Sidekick\transform_relative_path($path, $workingPath)
×
320
        : Sidekick\join_paths(rtrim($workingPath, DIRECTORY_SEPARATOR), $path);
93✔
321
}
322

323
/**
324
 * Get the workbench configuration.
325
 *
326
 * @api
327
 *
328
 * @return array<string, mixed>
329
 */
330
function workbench(): array
331
{
332
    /** @var \Orchestra\Testbench\Contracts\Config $config */
333
    $config = app()->bound(Contracts\Config::class)
48✔
334
        ? app()->make(Contracts\Config::class)
46✔
335
        : new Foundation\Config;
2✔
336

337
    return $config->getWorkbenchAttributes();
48✔
338
}
339

340
/**
341
 * Get the path to the workbench folder.
342
 *
343
 * @api
344
 *
345
 * @no-named-arguments
346
 *
347
 * @param  array<int, string|null>|string  ...$path
348
 * @return string
349
 */
350
function workbench_path(array|string $path = ''): string
351
{
352
    return package_path('workbench', ...Arr::wrap(\func_num_args() > 1 ? \func_get_args() : $path));
74✔
353
}
354

355
/**
356
 * Determine if vendor symlink exists on the laravel application.
357
 *
358
 * @api
359
 *
360
 * @param  \Illuminate\Contracts\Foundation\Application  $app
361
 * @param  string|null  $workingPath
362
 * @return bool
363
 */
364
function laravel_vendor_exists(ApplicationContract $app, ?string $workingPath = null): bool
365
{
366
    $filesystem = new Filesystem;
11✔
367

368
    $appVendorPath = $app->basePath('vendor');
11✔
369
    $workingPath ??= package_path('vendor');
11✔
370

371
    return $filesystem->isFile(Sidekick\join_paths($appVendorPath, 'autoload.php')) &&
11✔
372
        $filesystem->hash(Sidekick\join_paths($appVendorPath, 'autoload.php')) === $filesystem->hash(Sidekick\join_paths($workingPath, 'autoload.php'));
11✔
373
}
374

375
/**
376
 * Laravel version compare.
377
 *
378
 * @api
379
 *
380
 * @template TOperator of string|null
381
 *
382
 * @param  string  $version
383
 * @param  string|null  $operator
384
 *
385
 * @phpstan-param  TOperator  $operator
386
 *
387
 * @return int|bool
388
 *
389
 * @phpstan-return (TOperator is null ? int : bool)
390
 *
391
 * @codeCoverageIgnore
392
 */
393
function laravel_version_compare(string $version, ?string $operator = null): int|bool
394
{
395
    return Sidekick\laravel_version_compare($version, $operator);
396
}
397

398
/**
399
 * PHPUnit version compare.
400
 *
401
 * @api
402
 *
403
 * @template TOperator of string|null
404
 *
405
 * @param  string  $version
406
 * @param  string|null  $operator
407
 *
408
 * @phpstan-param  TOperator  $operator
409
 *
410
 * @return int|bool
411
 *
412
 * @phpstan-return (TOperator is null ? int : bool)
413
 *
414
 * @codeCoverageIgnore
415
 *
416
 * @throws \RuntimeException
417
 */
418
function phpunit_version_compare(string $version, ?string $operator = null): int|bool
419
{
420
    return Sidekick\phpunit_version_compare($version, $operator);
421
}
422

423
/**
424
 * Determine the PHP Binary.
425
 *
426
 * @api
427
 *
428
 * @param  bool  $escape
429
 * @return string
430
 */
431
function php_binary(bool $escape = false): string
432
{
433
    $phpBinary = Sidekick\php_binary();
12✔
434

435
    return $escape === true ? ProcessUtils::escapeArgument((string) $phpBinary) : $phpBinary;
12✔
436
}
437

438
/**
439
 * Join the given paths together.
440
 *
441
 * @param  string|null  $basePath
442
 * @param  string  ...$paths
443
 * @return string
444
 *
445
 * @codeCoverageIgnore
446
 */
447
function join_paths(?string $basePath, string ...$paths): string
448
{
449
    return Sidekick\join_paths($basePath, ...$paths);
450
}
451

452
/**
453
 * Ensure the provided `$app` return an instance of Laravel application or throw an exception.
454
 *
455
 * @internal
456
 *
457
 * @param  \Illuminate\Foundation\Application|null  $app
458
 * @param  string|null  $caller
459
 * @return \Illuminate\Foundation\Application
460
 *
461
 * @throws \Orchestra\Testbench\Exceptions\ApplicationNotAvailableException
462
 */
463
function laravel_or_fail($app, ?string $caller = null): Application
464
{
465
    if ($app instanceof Application) {
186✔
466
        return $app;
186✔
467
    }
468

469
    if (\is_null($caller)) {
1✔
470
        $caller = transform(debug_backtrace()[1] ?? null, function ($debug) {
1✔
471
            /** @phpstan-ignore isset.offset */
472
            if (isset($debug['class']) && isset($debug['function'])) {
1✔
473
                return \sprintf('%s::%s', $debug['class'], $debug['function']);
1✔
474
            }
475

476
            /** @phpstan-ignore offsetAccess.notFound */
477
            return $debug['function'];
×
478
        });
1✔
479
    }
480

481
    throw Exceptions\ApplicationNotAvailableException::make($caller);
1✔
482
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc