From 7fc327825ed51e26d355366dcf192fc07726b435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=93=E8=89=AF?= <1204183885@qq.com> Date: Thu, 29 Oct 2026 14:42:50 +0800 Subject: [PATCH] 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 --------- Co-authored-by: Qwen-Coder --- .../ManagedUserMessageStoreTest.java | 2 + scripts/check-failsafe-reports.js | 48 ++++++++++++++++++- scripts/tests/check-failsafe-reports.test.js | 32 +++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/packages/managed-agent-server/src/test/java/com/alibaba/qwen/code/managedagent/ManagedUserMessageStoreTest.java b/packages/managed-agent-server/src/test/java/com/alibaba/qwen/code/managedagent/ManagedUserMessageStoreTest.java index 05ae0c7059e..e0d0480a83d 100644 --- a/packages/managed-agent-server/src/test/java/com/alibaba/qwen/code/managedagent/ManagedUserMessageStoreTest.java +++ b/packages/managed-agent-server/src/test/java/com/alibaba/qwen/code/managedagent/ManagedUserMessageStoreTest.java @@ -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; diff --git a/scripts/check-failsafe-reports.js b/scripts/check-failsafe-reports.js index 7b40da792d7..a4a908aa5db 100644 --- a/scripts/check-failsafe-reports.js +++ b/scripts/check-failsafe-reports.js @@ -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(`**/${name}.java`) && + xml.includes(`**/${name}.java`) + ); +}; // 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( diff --git a/scripts/tests/check-failsafe-reports.test.js b/scripts/tests/check-failsafe-reports.test.js index 186a1fe3850..696819ae1da 100644 --- a/scripts/tests/check-failsafe-reports.test.js +++ b/scripts/tests/check-failsafe-reports.test.js @@ -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'), + '**/ManagedAgentPackagingIT.java**/ManagedAgentPackagingIT.java', + ); + 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'), + '**/ManagedAgentPackagingIT.java', + ); + 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(