Isolate invalid nodes using native error indices before bisection

This commit is contained in:
hellcat 2026-10-18 19:41:05 +08:00
parent 3d20330ee3
commit 7f657cb017
2 changed files with 40 additions and 1 deletions

View File

@ -13,6 +13,7 @@ final class NativeValidator
public array $rejected = [];
private string $dir;
private float $startedAt;
private ?int $failureIndex = null;
public function __construct(private string $singbox, private string $mihomo, private ?array $clashTemplate = null, ?string $dataDirectory = null)
{
@ -47,6 +48,13 @@ final class NativeValidator
try {
$process->run();
$this->checks++;
$this->failureIndex = null;
// Native diagnostics stay in memory. Extract only the structured node index.
$diagnostic = $process->getErrorOutput().$process->getOutput();
$pattern = $format === 'singbox' ? '/outbound(?:s)?\[(\d+)\]/' : '/proxy (\d+):/';
if (!$process->isSuccessful() && preg_match($pattern, $diagnostic, $match)) {
$this->failureIndex = (int) $match[1] - ($format === 'singbox' ? 2 : 0);
}
return $process->isSuccessful();
} finally {
@unlink($file);
@ -67,7 +75,16 @@ final class NativeValidator
private function partition(string $format, array $nodes, callable $render): array
{
if (!$nodes || $this->check($format, $render($nodes))) return $nodes;
if (!$nodes) return [];
while ($nodes) {
if ($this->check($format, $render($nodes))) return $nodes;
$keys = array_keys($nodes);
if ($this->failureIndex === null || !isset($keys[$this->failureIndex])) break;
$key = $keys[$this->failureIndex];
$this->rejected[$format][] = $key;
unset($nodes[$key]);
}
if (!$nodes) return [];
if (count($nodes) === 1) {
$this->rejected[$format][] = array_key_first($nodes);
return [];

View File

@ -0,0 +1,22 @@
<?php
namespace Tests\Unit\Freenode;
use App\Services\Freenode\NativeValidator;
use PHPUnit\Framework\TestCase;
final class NativeValidatorTest extends TestCase
{
public function testNativeErrorIndexAvoidsCheckingEveryNodeSeparately(): void
{
$mh = getenv('TEST_MIHOMO');
if (!$mh) self::markTestSkipped('Set TEST_MIHOMO for native validation');
$rows = [];
for ($i = 0; $i < 32; $i++) {
$rows[hash('sha256', (string) $i)] = ['name' => 'synthetic-'.$i, 'type' => 'ss', 'server' => 'example.invalid', 'port' => 443, 'cipher' => $i % 4 === 0 ? 'invalid-synthetic' : 'aes-128-gcm', 'password' => 'synthetic'];
}
$validator = new NativeValidator('', $mh);
$good = $validator->filter('clash', $rows);
self::assertCount(24, $good);
self::assertCount(8, $validator->rejected['clash']);
self::assertLessThanOrEqual(10, $validator->checks);
}
}