fix(ci): exempt surefire-bound IT classes from the failsafe roster (#15830)

* fix(ci): exempt surefire-bound IT classes from the failsafe roster

The non-hosted roster required every *IT class to appear in
target/failsafe-reports, but packages/managed-agent-server binds
ManagedAgentPackagingIT to the artifact-packaging surefire execution and
excludes it from the mysql-integration failsafe (#15718). A class that is
forbidden to run under failsafe therefore could never satisfy the roster:
Maven reported BUILD SUCCESS with the class's 3 tests passing, then the job
failed on 'ran no test in this job'. It reproduces on main with no PR code
present (#15820 item 1) and turned the full tier red for every managed-agent
PR.

Name the surefire-bound classes per module, skip them in the roster, and fail
the check if the module's pom stops binding such a name to both a surefire
include and a failsafe exclude, so the exemption cannot drift away from the
pom it describes.

* test(managed-agent): use managed JDBC for message fixture

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>
This commit is contained in:
易良andQwen-Coder authored and GitHub committed 2026-10-29 14:42:50 +08:00
1 parent 1211409492
commit 7fc327825e
3 files changed
+81 -1

No files matched your search

@@ -13,6 +13,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Transactional;
@@ -43,6 +44,7 @@ class ManagedUserMessageStoreTest {
@Autowired
private ManagedAgentService agents;
@Autowired
@Qualifier("managedJdbcTemplate")
private JdbcTemplate jdbc;
@Autowired
private ObjectMapper json;
+47 -1
View File
@@ -48,6 +48,38 @@ if (!inFamily || modules.length === 0) {
}
const simpleName = (className) => className.split('.').at(-1);
// A module can bind an `*IT` class to a surefire execution instead of failsafe.
// Such a class is then excluded from the mysql-integration failsafe (so it can
// never have a report) while still matching the non-hosted roster below, which
// is a contradiction rather than a missed run: #15820 item 1. Name each such
// class here, and keep the pom in agreement — the check below fails if a name
// is not bound to a surefire include and a failsafe exclude in that module.
const SUREFIRE_BOUND = new Map([
['packages/managed-agent-server', ['ManagedAgentPackagingIT']],
]);
// The module argument is whatever the job passed to Maven: relative on CI,
// absolute in the meta-tests, so match its trailing segments.
const boundNames = (module) => {
const normalized = path.normalize(module).split(path.sep).join('/');
for (const [dir, names] of SUREFIRE_BOUND) {
if (normalized === dir || normalized.endsWith(`/${dir}`)) return names;
}
return [];
};
const rosterExempt = (module, name) => boundNames(module).includes(name);
const pomBindsBoth = (module, name) => {
const pom = path.join(module, 'pom.xml');
if (!existsSync(pom)) return false;
const xml = readFileSync(pom, 'utf8');
return (
xml.includes(`<include>**/${name}.java</include>`) &&
xml.includes(`<exclude>**/${name}.java</exclude>`)
);
};
// Not readdirSync's `recursive`: a Node older than 18.17 ignores it, and the
// check would then find no integration test at all.
const javaFiles = (dir, prefix = []) =>
@@ -61,7 +93,21 @@ for (const module of modules) {
const expected = javaFiles(path.join(module, 'src', 'test', 'java'))
.filter((file) => file.endsWith('.java'))
.map((file) => file.slice(0, -'.java'.length))
.filter((className) => inFamily(simpleName(className)));
.filter((className) => inFamily(simpleName(className)))
.filter((className) => {
const name = simpleName(className);
if (!rosterExempt(module, name)) return true;
console.log(`${module}: ${name} runs under surefire, not failsafe`);
return false;
});
for (const name of boundNames(module)) {
if (!pomBindsBoth(module, name)) {
failed = true;
console.error(
`::error::${module}: ${name} is listed as surefire-bound but its pom no longer binds it to a surefire include and a failsafe exclude`,
);
}
}
if (optIn && expected.length !== 1) {
failed = true;
console.error(
@@ -148,6 +148,38 @@ describe('check-failsafe-reports', () => {
expect(check('hosted', hosted).status).toBe(0);
});
it('exempts a class the module binds to surefire, and fails when the pom disagrees', () => {
// The real module name, because the exemption is keyed on it: the pom binds
// ManagedAgentPackagingIT to a surefire execution and excludes it from the
// mysql-integration failsafe, so it can never satisfy the non-hosted roster.
const name = 'packages/managed-agent-server';
const sources = [
'com.example.ManagedAgentPackagingIT',
'com.example.OrdersIT',
];
const bound = module(name, sources, { 'com.example.OrdersIT': [1, 0] });
writeFileSync(
join(bound, 'pom.xml'),
'<project><plugin><configuration><includes><include>**/ManagedAgentPackagingIT.java</include></includes></configuration></plugin><plugin><configuration><excludes><exclude>**/ManagedAgentPackagingIT.java</exclude></excludes></configuration></plugin></project>',
);
const exempt = check('non-hosted', bound);
expect(exempt.output).toContain(
'ManagedAgentPackagingIT runs under surefire, not failsafe',
);
expect(exempt.status).toBe(0);
// Drift guard: the exemption is only valid while the pom keeps both bindings.
writeFileSync(
join(bound, 'pom.xml'),
'<project><plugin><configuration><excludes><exclude>**/ManagedAgentPackagingIT.java</exclude></excludes></configuration></plugin></project>',
);
const drifted = check('non-hosted', bound);
expect(drifted.output).toContain(
'no longer binds it to a surefire include',
);
expect(drifted.status).toBe(1);
});
it('counts the test cases failsafe folds into an outer report', () => {
const dir = module('nested', ['com.example.OrdersIT']);
writeFileSync(