in src/Retriever/ClassRetriever.hack [85:154]
public function getTestClassName(): ?classname<HackTest> {
$test_classes = $this->caseInsensitiveClassnames
|> Vec\filter(
$$,
$name ==> \is_subclass_of($name, HackTest::class, true),
);
$count = C\count($test_classes);
if ($count !== 1) {
$all_classes = '';
if (!C\is_empty($this->caseInsensitiveClassnames)) {
$all_classes = ':';
foreach ($this->caseInsensitiveClassnames as $cn) {
$rc = new \ReflectionClass($cn);
$all_classes .= "\n - ".$rc->getName();
if ($rc->isSubclassOf(HackTest::class)) {
$all_classes .= ' (is a test class)';
} else {
$all_classes .= ' (is not a subclass of '.HackTest::class.')';
}
}
}
throw new InvalidTestClassException(
Str\format(
'There must be exactly one test class in %s; found %d%s',
$this->filename,
$count,
$all_classes,
),
);
}
$rc = new \ReflectionClass(C\onlyx($test_classes));
if ($rc->isAbstract()) {
return null;
}
$name = $rc->getName(); // fixes capitalization
$class_name = $name
|> Str\split($$, '\\')
|> C\lastx($$);
$filename = $this->filename
|> Str\split($$, '/')
|> C\lastx($$)
|> Str\split($$, '.')
|> C\firstx($$);
if ($class_name !== $filename) {
throw new InvalidTestClassException(
Str\format(
'Class name (%s) must match filename (%s)',
$class_name,
$filename,
),
);
}
if (!Str\ends_with($class_name, 'Test')) {
throw new InvalidTestClassException(
Str\format('Class name (%s) must end with Test', $class_name),
);
}
$classname = $this->convertToClassname($name);
if ($classname === null) {
throw new InvalidTestClassException(
Str\format('%s does not extend %s', $name, HackTest::class),
);
}
return $classname;
}