test: header-change incrementality across every compile path
Some checks failed
CI / build-test-release (pull_request) Failing after 12m4s

One header per pass, asserting both directions — the objects that include
it recompile, the ones that don't are untouched — then running the binary,
since "was recompiled" only matters if the program agrees with itself.

Covers the module interface (a macro in its global module fragment decides
an exported class's layout), the implementation unit, a C source, an idle
rebuild that must recompile nothing, and an object whose depfile is gone.
The implementation unit's header has a space in its name so the depfile
spells it escaped.
This commit is contained in:
catbot 2026-07-31 11:11:49 +00:00
commit 7975cb1df8
12 changed files with 317 additions and 0 deletions

View file

@ -0,0 +1,11 @@
// SPDX-License-Identifier: LGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
module;
#include "widget count.h"
module Widget;
import std;
std::size_t WidgetSizeInLibrary() { return sizeof(Widget); }
std::int64_t WidgetCountInLibrary() { return WIDGET_COUNT; }

View file

@ -0,0 +1,21 @@
// SPDX-License-Identifier: LGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
module;
// The whole point of the fixture: an input to this interface unit that the
// module scanner cannot see. WIDGET_SLOTS comes from a header, so the class
// layout below changes without Widget.cppm being touched.
#include "widget-layout.h"
export module Widget;
import std;
export struct Widget {
std::array<std::int64_t, WIDGET_SLOTS> slots;
};
// Out-of-line in the library's implementation unit, so the value reflects the
// layout the *library* was compiled against rather than the caller's — same
// reasoning as the IncrementalInterfaceChange fixture.
export std::size_t WidgetSizeInLibrary();
// Reads a macro from a header only the implementation unit includes.
export std::int64_t WidgetCountInLibrary();

View file

@ -0,0 +1,7 @@
/* SPDX-License-Identifier: LGPL-3.0-only
SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® */
/* Copied over lib/counter-limit.h mid-test. */
#pragma once
#define COUNTER_LIMIT 9

View file

@ -0,0 +1,8 @@
/* SPDX-License-Identifier: LGPL-3.0-only
SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® */
/* Included from counter.c — the C compile path keeps all of its dependencies in
#includes, so it has nothing but the depfile to go on. */
#pragma once
#define COUNTER_LIMIT 5

View file

@ -0,0 +1,8 @@
/* SPDX-License-Identifier: LGPL-3.0-only
SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® */
#include "counter-limit.h"
long CounterLimitInLibrary(void) {
return COUNTER_LIMIT;
}

View file

@ -0,0 +1,7 @@
// SPDX-License-Identifier: LGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
// Copied over lib/widget count.h mid-test.
#pragma once
#define WIDGET_COUNT 2

View file

@ -0,0 +1,12 @@
// SPDX-License-Identifier: LGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
// Included from Widget.cpp, the module's implementation unit — nothing else
// sees it, so an edit here must rebuild that one object and nothing more.
//
// The space in the filename is deliberate: clang escapes it as `widget\ count.h`
// in the depfile, so a build that reads depfiles has to unescape make syntax
// rather than splitting the line on whitespace.
#pragma once
#define WIDGET_COUNT 1

View file

@ -0,0 +1,9 @@
// SPDX-License-Identifier: LGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
// Copied over lib/widget-layout.h mid-test: one more slot in the exported
// class. Kept as a .h.in so the original header is the only one any source
// #includes.
#pragma once
#define WIDGET_SLOTS 2

View file

@ -0,0 +1,11 @@
// SPDX-License-Identifier: LGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
// Included from Widget.cppm's global module fragment. WIDGET_SLOTS decides the
// layout of the exported class, so growing it (the test copies
// widget-layout-grown.h.in over this file) invalidates the BMI and every object
// compiled against it — while leaving Widget.cppm itself untouched, which is
// exactly what the mtime comparison used to miss.
#pragma once
#define WIDGET_SLOTS 1

View file

@ -0,0 +1,19 @@
// SPDX-License-Identifier: LGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
import std;
import Widget;
extern "C" long CounterLimitInLibrary();
// Prints "<sizeof here> <sizeof in library> <count> <limit>" and exits 1 when
// the two sizes disagree. The sizes are the mixed-layout check; the count and
// the limit report which build of the library's own objects got linked in, so a
// stale implementation object or C object shows up as an old value rather than
// as silence.
int main() {
std::size_t here = sizeof(Widget);
std::size_t inLibrary = WidgetSizeInLibrary();
std::print("{} {} {} {}", here, inLibrary, WidgetCountInLibrary(), CounterLimitInLibrary());
return here == inLibrary ? 0 : 1;
}