Revision control

++ // Copyright (C) 2026 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ //
++ // scanner_diff_selftest — exercises classesDeclaredIn()/classesDefinedIn()
++ // (the real scanner functions qtdiff uses, not a reimplementation) against
++ // hand-written header/source text fixtures. No filesystem, no Qt
++ // dependency — runs identically and quickly on macOS, Windows, and Linux.
++
++ #include "scanner.h"
++
++ #include <iostream>
++ #include <string>
++
++ using namespace QtFuzz;
++
++ namespace {
++
++ int g_failures = 0;
++
++ void check(bool cond, const std::string &what)
++ {
++ if (!cond) {
++ std::cerr << "FAIL: " << what << "\n";
++ ++g_failures;
++ } else {
++ std::cout << "ok: " << what << "\n";
++ }
++ }
++
++ bool contains(const std::vector<std::string> &v, const std::string &name)
++ {
++ for (const auto &n : v) {
++ if (n == name)
++ return true;
++ }
++ return false;
++ }
++
++ } // namespace
++
++ int main()
++ {
++ // ── classesDeclaredIn: multiple classes in one header ────────────────────
++ {
++ const std::string header = R"(
++ class Q_CORE_EXPORT QFoo {
++ public:
++ QFoo();
++ void bar();
++ };
++
++ class QBar : public QFoo {
++ public:
++ QBar();
++ };
++ )";
++ auto names = classesDeclaredIn(header);
++ check(contains(names, "QFoo"), "classesDeclaredIn finds exported class QFoo");
++ check(contains(names, "QBar"), "classesDeclaredIn finds unexported class QBar");
++ check(names.size() == 2, "classesDeclaredIn finds exactly 2 classes");
++ }
++
++ // ── classesDeclaredIn: Private/Impl/_p suffixes are excluded ─────────────
++ {
++ const std::string header = R"(
++ class QFooPrivate {
++ public:
++ int state;
++ };
++ class QFoo {
++ public:
++ QFoo();
++ };
++ )";
++ auto names = classesDeclaredIn(header);
++ check(!contains(names, "QFooPrivate"), "classesDeclaredIn excludes *Private classes");
++ check(contains(names, "QFoo"), "classesDeclaredIn still finds QFoo alongside excluded Private");
++ }
++
++ // ── classesDeclaredIn: no Q-prefixed class in the file ────────────────────
++ {
++ const std::string header = R"(
++ class NetworkTest {
++ public:
++ NetworkTest();
++ };
++ )";
++ auto names = classesDeclaredIn(header);
++ check(names.empty(), "classesDeclaredIn returns nothing for a non-Q-prefixed class");
++ }
++
++ // ── classesDefinedIn: out-of-line definitions at depth 0 ─────────────────
++ {
++ const std::string source = R"(
++ #include "qfoo.h"
++
++ QFoo::QFoo()
++ {
++ doSomethingUnrelated();
++ }
++
++ void QFoo::bar()
++ {
++ QOther::helper(); // nested call inside a method body — must not count
++ }
++
++ void QBar::baz()
++ {
++ }
++ )";
++ auto names = classesDefinedIn(source);
++ check(contains(names, "QFoo"), "classesDefinedIn finds QFoo from QFoo::bar() definition");
++ check(contains(names, "QBar"), "classesDefinedIn finds QBar from QBar::baz() definition");
++ check(!contains(names, "QOther"),
++ "classesDefinedIn ignores QOther::helper() call inside a method body (depth > 0)");
++ check(names.size() == 2, "classesDefinedIn finds exactly 2 classes");
++ }
++
++ // ── classesDefinedIn: no qualified definitions ────────────────────────────
++ {
++ const std::string source = R"(
++ int main() { return 0; }
++ )";
++ auto names = classesDefinedIn(source);
++ check(names.empty(), "classesDefinedIn returns nothing when no Class::member(...) exists");
++ }
++
++ // ── classesDefinedIn: a string literal ending in an escaped backslash
++ // ("C:\\", i.e. quote C : backslash backslash quote) must not be
++ // mistaken for an escaped closing quote. A single preceding backslash
++ // escapes a quote, but a *pair* of backslashes is itself one escaped
++ // backslash — the quote right after it is unescaped and really does
++ // close the string. Getting this wrong leaves the parser stuck
++ // believing it's still inside the string for the rest of the file,
++ // which would make it miss every later definition (QBar::baz() here).
++ {
++ const std::string source = R"(
++ void QFoo::setPath()
++ {
++ const char *p = "C:\\";
++ }
++
++ void QBar::baz()
++ {
++ }
++ )";
++ auto names = classesDefinedIn(source);
++ check(contains(names, "QFoo"),
++ "classesDefinedIn finds QFoo despite the C:\\\\ string literal");
++ check(contains(names, "QBar"),
++ "classesDefinedIn still finds QBar::baz() after the escaped-backslash string");
++ }
++
++ if (g_failures > 0) {
++ std::cerr << g_failures << " check(s) failed.\n";
++ return 1;
++ }
++ std::cout << "All checks passed.\n";
++ return 0;
++ }