mirror of
https://github.com/pestphp/pest.git
synced 2026-06-05 02:52:12 +02:00
wip
This commit is contained in:
@ -367,6 +367,15 @@ YAML;
|
||||
return $m[1];
|
||||
}
|
||||
|
||||
// SSH URL form: ssh://[user@]github.com[:port]/org/repo(.git).
|
||||
// Some teams configure this explicitly to pin the SSH port; the
|
||||
// colon-separated form above doesn't match. Mirrors the parser
|
||||
// in `Storage::originIdentity` so the same remote produces the
|
||||
// same project key for both storage and remote-fetch.
|
||||
if (preg_match('#^ssh://(?:[^@/]+@)?github\.com(?::\d+)?/([\w.-]+/[\w.-]+?)(?:\.git)?/?$#i', $url, $m) === 1) {
|
||||
return $m[1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ namespace Pest\Plugins\Tia;
|
||||
|
||||
use Pest\Support\Container;
|
||||
use PHPUnit\Framework\TestStatus\TestStatus;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* File-level Test Impact Analysis graph.
|
||||
@ -319,7 +320,21 @@ final class Graph
|
||||
if ($newJsFiles !== []) {
|
||||
$freshMap = JsModuleGraph::buildStrict($this->projectRoot);
|
||||
|
||||
if ($freshMap !== null) {
|
||||
if ($freshMap === null) {
|
||||
// Vite resolver was unavailable (Node missing, cold-start
|
||||
// timeout, vite.config refused to load). Falling back to
|
||||
// the broad watch pattern is the correct call, but
|
||||
// doing so silently can make a slow replay feel
|
||||
// inexplicable — surface a single line so the user
|
||||
// knows precision was downgraded for these files.
|
||||
$output = Container::getInstance()->get(OutputInterface::class);
|
||||
if ($output instanceof OutputInterface) {
|
||||
$output->writeln(sprintf(
|
||||
' <fg=yellow>TIA</> Vite resolver unavailable — falling back to watch pattern for %d new JS file(s).',
|
||||
count($newJsFiles),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
foreach ($newJsFiles as $rel) {
|
||||
$pages = $freshMap[$rel] ?? [];
|
||||
|
||||
|
||||
@ -100,21 +100,19 @@ final class Recorder
|
||||
if (function_exists('pcov\\start')) {
|
||||
$this->driver = 'pcov';
|
||||
$this->driverAvailable = true;
|
||||
} elseif (function_exists('xdebug_start_code_coverage')) {
|
||||
// Xdebug is loaded. Probe whether coverage mode is active by
|
||||
// attempting a start — it emits E_WARNING when the mode is off.
|
||||
// We capture the warning via a temporary error handler.
|
||||
$probeOk = true;
|
||||
set_error_handler(static function () use (&$probeOk): bool {
|
||||
$probeOk = false;
|
||||
} elseif (function_exists('xdebug_start_code_coverage') && function_exists('xdebug_info')) {
|
||||
// Xdebug 3+ exposes the active mode set via `xdebug_info`,
|
||||
// so we can ask directly instead of probing with a
|
||||
// start/stop pair. The probe approach used to emit
|
||||
// E_WARNING when coverage mode was off; with monitoring
|
||||
// agents (Sentry, Bugsnag) hooked into the error
|
||||
// handler stack that warning could be reported as a
|
||||
// real error. `xdebug_info('mode')` is silent and
|
||||
// returns the active modes as a list, so a presence
|
||||
// check is enough.
|
||||
$modes = \xdebug_info('mode');
|
||||
|
||||
return true;
|
||||
});
|
||||
\xdebug_start_code_coverage();
|
||||
restore_error_handler();
|
||||
|
||||
if ($probeOk) {
|
||||
\xdebug_stop_code_coverage(false);
|
||||
if (is_array($modes) && in_array('coverage', $modes, true)) {
|
||||
$this->driver = 'xdebug';
|
||||
$this->driverAvailable = true;
|
||||
}
|
||||
|
||||
@ -105,21 +105,27 @@ final class TableExtractor
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string> Table names referenced by `Schema::` calls
|
||||
* OR raw DDL statements in the given migration
|
||||
* @return list<string> Table names referenced by `Schema::` calls,
|
||||
* raw DDL, or DML inside the given migration
|
||||
* file contents. Empty when nothing matches —
|
||||
* callers treat that as "fall back to the
|
||||
* broad watch pattern".
|
||||
*
|
||||
* Two passes:
|
||||
* Three passes:
|
||||
* 1. `Schema::create|table|drop|dropIfExists|dropColumn[s]|rename`
|
||||
* captures the conventional Laravel migration shape.
|
||||
* 2. Raw DDL fallback: scans for `CREATE / ALTER / DROP /
|
||||
* TRUNCATE / RENAME TABLE <name>` patterns inside string
|
||||
* literals (i.e. `DB::statement('CREATE TABLE …')`,
|
||||
* `DB::unprepared('ALTER TABLE …')`). False positives possible
|
||||
* if the same syntax appears in a comment or unrelated string,
|
||||
* but over-attribution is correctness-safe.
|
||||
* `DB::unprepared('ALTER TABLE …')`).
|
||||
* 3. DML inside migration bodies — `INSERT INTO`, `UPDATE … SET`,
|
||||
* `DELETE FROM`, and Laravel's fluent `DB::table('foo')`.
|
||||
* Catches the seeded-lookup-table case where a migration
|
||||
* populates rows that tests later read.
|
||||
*
|
||||
* False positives possible when the same syntax appears in a
|
||||
* comment or unrelated string, but over-attribution is
|
||||
* correctness-safe.
|
||||
*/
|
||||
public static function fromMigrationSource(string $php): array
|
||||
{
|
||||
@ -157,6 +163,33 @@ final class TableExtractor
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 3: DML inside migration bodies. Migrations that seed
|
||||
// lookup tables via `DB::statement('INSERT INTO roles …')`,
|
||||
// `DB::table('statuses')->insert(…)`, `UPDATE foo SET …`, or
|
||||
// `DELETE FROM bar` are common in Laravel. Without picking
|
||||
// these up, an edit to the seed payload would route through
|
||||
// only the schema'd tables and silently skip every test that
|
||||
// reads from the populated table. Fluent-builder calls
|
||||
// (`DB::table('x')`) and raw SQL strings are both covered.
|
||||
$dmlPatterns = [
|
||||
'/INSERT\s+(?:IGNORE\s+)?INTO\s+["`\[]?(\w+)["`\]]?/i',
|
||||
'/UPDATE\s+["`\[]?(\w+)["`\]]?\s+SET\b/i',
|
||||
'/DELETE\s+FROM\s+["`\[]?(\w+)["`\]]?/i',
|
||||
'/DB::table\(\s*[\'"]([^\'"]+)[\'"]\s*\)/',
|
||||
];
|
||||
|
||||
foreach ($dmlPatterns as $pattern) {
|
||||
if (preg_match_all($pattern, $php, $matches) === false) {
|
||||
continue;
|
||||
}
|
||||
foreach ($matches[1] as $name) {
|
||||
$lower = strtolower($name);
|
||||
if (! self::isSchemaMeta($lower)) {
|
||||
$tables[$lower] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$out = array_keys($tables);
|
||||
sort($out);
|
||||
|
||||
|
||||
@ -43,12 +43,18 @@ final readonly class Inertia implements WatchDefault
|
||||
'resources/js/Pages/**/*.tsx' => [$browserDir],
|
||||
'resources/js/Pages/**/*.jsx' => [$browserDir],
|
||||
'resources/js/Pages/**/*.svelte' => [$browserDir],
|
||||
'resources/js/Pages/**/*.ts' => [$browserDir],
|
||||
'resources/js/Pages/**/*.js' => [$browserDir],
|
||||
|
||||
// Shared layouts / components consumed by pages.
|
||||
'resources/js/Layouts/**/*.vue' => [$browserDir],
|
||||
'resources/js/Layouts/**/*.tsx' => [$browserDir],
|
||||
'resources/js/Layouts/**/*.ts' => [$browserDir],
|
||||
'resources/js/Layouts/**/*.js' => [$browserDir],
|
||||
'resources/js/Components/**/*.vue' => [$browserDir],
|
||||
'resources/js/Components/**/*.tsx' => [$browserDir],
|
||||
'resources/js/Components/**/*.ts' => [$browserDir],
|
||||
'resources/js/Components/**/*.js' => [$browserDir],
|
||||
|
||||
// SSR entry point.
|
||||
'resources/js/ssr.js' => [$browserDir],
|
||||
|
||||
Reference in New Issue
Block a user