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

orchestral / testbench-core / 17281482506

27 Aug 2025 11:43PM UTC coverage: 92.242% (-0.2%) from 92.41%
17281482506

Pull #355

github

web-flow
Merge ea4eddbb2 into 4446be291
Pull Request #355: Add `Orchestra\Testbench\terminate()` function

0 of 3 new or added lines in 1 file covered. (0.0%)

5 existing lines in 1 file now uncovered.

1522 of 1650 relevant lines covered (92.24%)

75.63 hits per line

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

91.3
/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\Runner\ShutdownHandler;
19

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

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

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

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

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

65
/**
66
 * Emit an exit event within a test.
67
 *
68
 * @param  \Orchestra\Testbench\Contracts\TestCase  $testCase
69
 * @param  string|int  $status
70
 * @return never
71
 */
72
function terminate(Contracts\TestCase $testCase, string|int $status = 0): never
73
{
NEW
74
    if (Sidekick\phpunit_version_compare('12.3.5', '>=')) {
×
NEW
75
        ShutdownHandler::resetMessage();
×
76
    }
77

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

81
/**
82
 * Run remote action using Testbench CLI.
83
 *
84
 * @api
85
 *
86
 * @param  (\Closure():(mixed))|array<int, string>|string  $command
87
 * @param  array<string, mixed>|string  $env
88
 * @param  bool|null  $tty
89
 * @return \Orchestra\Testbench\Foundation\Process\ProcessDecorator
90
 */
91
function remote(Closure|array|string $command, array|string $env = [], ?bool $tty = null): Foundation\Process\ProcessDecorator
92
{
93
    $remote = new Foundation\Process\RemoteCommand(
12✔
94
        package_path(), $env, $tty
12✔
95
    );
12✔
96

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

99
    $commander = is_file($vendorBinary = package_path('vendor', 'bin', $binary))
12✔
UNCOV
100
        ? $vendorBinary
×
101
        : $binary;
12✔
102

103
    return $remote->handle($commander, $command);
12✔
104
}
105

106
/**
107
 * Register after resolving callback.
108
 *
109
 * @api
110
 *
111
 * @param  \Illuminate\Contracts\Foundation\Application  $app
112
 * @param  string  $name
113
 * @param  (\Closure(object, \Illuminate\Contracts\Foundation\Application):(mixed))|null  $callback
114
 * @return void
115
 */
116
function after_resolving(ApplicationContract $app, string $name, ?Closure $callback = null): void
117
{
118
    $app->afterResolving($name, $callback);
193✔
119

120
    if ($app->resolved($name)) {
193✔
121
        value($callback, $app->make($name), $app);
1✔
122
    }
123
}
124

125
/**
126
 * Load migration paths.
127
 *
128
 * @api
129
 *
130
 * @param  \Illuminate\Contracts\Foundation\Application  $app
131
 * @param  array<int, string>|string  $paths
132
 * @return void
133
 */
134
function load_migration_paths(ApplicationContract $app, array|string $paths): void
135
{
136
    after_resolving($app, 'migrator', static function ($migrator) use ($paths) {
51✔
137
        foreach (Arr::wrap($paths) as $path) {
15✔
138
            /** @var \Illuminate\Database\Migrations\Migrator $migrator */
139
            $migrator->path($path);
15✔
140
        }
141
    });
51✔
142
}
143

144
/**
145
 * Get defined environment variables.
146
 *
147
 * @api
148
 *
149
 * @return array<string, mixed>
150
 */
151
function defined_environment_variables(): array
152
{
153
    return (new Collection(array_merge($_SERVER, $_ENV)))
12✔
154
        ->keys()
12✔
155
        ->mapWithKeys(static fn (string $key) => [$key => Sidekick\Env::forward($key)])
12✔
156
        ->unless(
12✔
157
            Sidekick\Env::has('TESTBENCH_WORKING_PATH'), static fn ($env) => $env->put('TESTBENCH_WORKING_PATH', package_path())
12✔
158
        )->all();
12✔
159
}
160

161
/**
162
 * Get default environment variables.
163
 *
164
 * @api
165
 *
166
 * @param  iterable<string, mixed>  $variables
167
 * @return array<int, string>
168
 */
169
function parse_environment_variables($variables): array
170
{
171
    return (new Collection($variables))
4✔
172
        ->transform(static function ($value, $key) {
4✔
173
            if (\is_bool($value) || \in_array($value, ['true', 'false'])) {
4✔
174
                $value = \in_array($value, [true, 'true']) ? '(true)' : '(false)';
4✔
175
            } elseif (\is_null($value) || \in_array($value, ['null'])) {
1✔
176
                $value = '(null)';
1✔
177
            } else {
178
                $value = $key === 'APP_DEBUG' ? \sprintf('(%s)', Str::of($value)->ltrim('(')->rtrim(')')) : "'{$value}'";
1✔
179
            }
180

181
            return "{$key}={$value}";
4✔
182
        })->values()->all();
4✔
183
}
184

185
/**
186
 * Refresh router lookups.
187
 *
188
 * @api
189
 *
190
 * @param  \Illuminate\Routing\Router  $router
191
 * @return void
192
 */
193
function refresh_router_lookups(Router $router): void
194
{
195
    $router->getRoutes()->refreshNameLookups();
193✔
196
}
197

198
/**
199
 * Transform realpath to alias path.
200
 *
201
 * @api
202
 *
203
 * @param  string  $path
204
 * @param  string|null  $workingPath
205
 * @return string
206
 */
207
function transform_realpath_to_relative(string $path, ?string $workingPath = null, string $prefix = ''): string
208
{
209
    $separator = DIRECTORY_SEPARATOR;
13✔
210

211
    if (! \is_null($workingPath)) {
13✔
212
        return str_replace(rtrim($workingPath, $separator).$separator, $prefix.$separator, $path);
1✔
213
    }
214

215
    $laravelPath = base_path();
12✔
216
    $workbenchPath = workbench_path();
12✔
217
    $packagePath = package_path();
12✔
218

219
    return match (true) {
220
        str_starts_with($path, $laravelPath) => str_replace($laravelPath.$separator, '@laravel'.$separator, $path),
12✔
221
        str_starts_with($path, $workbenchPath) => str_replace($workbenchPath.$separator, '@workbench'.$separator, $path),
8✔
222
        str_starts_with($path, $packagePath) => str_replace($packagePath.$separator, '.'.$separator, $path),
8✔
223
        ! empty($prefix) => implode($separator, [$prefix, ltrim($path, $separator)]),
8✔
224
        default => $path,
12✔
225
    };
226
}
227

228
/**
229
 * Get the default skeleton path.
230
 *
231
 * @api
232
 *
233
 * @no-named-arguments
234
 *
235
 * @param  array<int, string|null>|string  ...$path
236
 * @return string
237
 */
238
function default_skeleton_path(array|string $path = ''): string
239
{
240
    return (string) realpath(
190✔
241
        Sidekick\join_paths(__DIR__, '..', 'laravel', ...Arr::wrap(\func_num_args() > 1 ? \func_get_args() : $path))
190✔
242
    );
190✔
243
}
244

245
/**
246
 * Get the migration path by type.
247
 *
248
 * @api
249
 *
250
 * @param  string|null  $type
251
 * @return string
252
 *
253
 * @throws \InvalidArgumentException
254
 */
255
function default_migration_path(?string $type = null): string
256
{
257
    $path = realpath(
59✔
258
        \is_null($type) ? base_path('migrations') : base_path(Sidekick\join_paths('migrations', $type))
59✔
259
    );
59✔
260

261
    if ($path === false) {
59✔
UNCOV
262
        throw new InvalidArgumentException(\sprintf('Unable to resolve migration path for type [%s]', $type ?? 'laravel'));
×
263
    }
264

265
    return $path;
59✔
266
}
267

268
/**
269
 * Get the path to the package folder.
270
 *
271
 * @api
272
 *
273
 * @no-named-arguments
274
 *
275
 * @param  array<int, string|null>|string  ...$path
276
 * @return string
277
 */
278
function package_path(array|string $path = ''): string
279
{
280
    $argumentCount = \func_num_args();
93✔
281

282
    $workingPath = \defined('TESTBENCH_WORKING_PATH')
93✔
283
        ? TESTBENCH_WORKING_PATH
54✔
284
        : Sidekick\Env::get('TESTBENCH_WORKING_PATH', getcwd());
39✔
285

286
    if ($argumentCount === 1 && \is_string($path) && str_starts_with($path, './')) {
93✔
287
        return Sidekick\transform_relative_path($path, $workingPath);
7✔
288
    }
289

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

292
    return str_starts_with($path, './')
93✔
UNCOV
293
        ? Sidekick\transform_relative_path($path, $workingPath)
×
294
        : Sidekick\join_paths(rtrim($workingPath, DIRECTORY_SEPARATOR), $path);
93✔
295
}
296

297
/**
298
 * Get the workbench configuration.
299
 *
300
 * @api
301
 *
302
 * @return array<string, mixed>
303
 */
304
function workbench(): array
305
{
306
    /** @var \Orchestra\Testbench\Contracts\Config $config */
307
    $config = app()->bound(Contracts\Config::class)
48✔
308
        ? app()->make(Contracts\Config::class)
46✔
309
        : new Foundation\Config;
2✔
310

311
    return $config->getWorkbenchAttributes();
48✔
312
}
313

314
/**
315
 * Get the path to the workbench folder.
316
 *
317
 * @api
318
 *
319
 * @no-named-arguments
320
 *
321
 * @param  array<int, string|null>|string  ...$path
322
 * @return string
323
 */
324
function workbench_path(array|string $path = ''): string
325
{
326
    return package_path('workbench', ...Arr::wrap(\func_num_args() > 1 ? \func_get_args() : $path));
74✔
327
}
328

329
/**
330
 * Get the migration path by type.
331
 *
332
 * @api
333
 *
334
 * @param  string|null  $type
335
 * @return string
336
 *
337
 * @throws \InvalidArgumentException
338
 *
339
 * @deprecated
340
 *
341
 * @codeCoverageIgnore
342
 */
343
#[\Deprecated(message: 'Use `Orchestra\Testbench\default_migration_path()` instead', since: '9.5.1')]
344
function laravel_migration_path(?string $type = null): string
345
{
346
    return default_migration_path($type);
347
}
348

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

362
    $appVendorPath = $app->basePath('vendor');
11✔
363
    $workingPath ??= package_path('vendor');
11✔
364

365
    return $filesystem->isFile(Sidekick\join_paths($appVendorPath, 'autoload.php')) &&
11✔
366
        $filesystem->hash(Sidekick\join_paths($appVendorPath, 'autoload.php')) === $filesystem->hash(Sidekick\join_paths($workingPath, 'autoload.php'));
11✔
367
}
368

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

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

417
/**
418
 * Determine the PHP Binary.
419
 *
420
 * @api
421
 *
422
 * @param  bool  $escape
423
 * @return string
424
 */
425
function php_binary(bool $escape = false): string
426
{
427
    $phpBinary = Sidekick\php_binary();
12✔
428

429
    return $escape === true ? ProcessUtils::escapeArgument((string) $phpBinary) : $phpBinary;
12✔
430
}
431

432
/**
433
 * Join the given paths together.
434
 *
435
 * @param  string|null  $basePath
436
 * @param  string  ...$paths
437
 * @return string
438
 *
439
 * @codeCoverageIgnore
440
 */
441
function join_paths(?string $basePath, string ...$paths): string
442
{
443
    return Sidekick\join_paths($basePath, ...$paths);
444
}
445

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

463
    if (\is_null($caller)) {
1✔
464
        $caller = transform(debug_backtrace()[1] ?? null, function ($debug) {
1✔
465
            /** @phpstan-ignore isset.offset */
466
            if (isset($debug['class']) && isset($debug['function'])) {
1✔
467
                return \sprintf('%s::%s', $debug['class'], $debug['function']);
1✔
468
            }
469

470
            /** @phpstan-ignore offsetAccess.notFound */
UNCOV
471
            return $debug['function'];
×
472
        });
1✔
473
    }
474

475
    throw Exceptions\ApplicationNotAvailableException::make($caller);
1✔
476
}
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