feat: archive zoo_backup for home sync
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
import { parametersSchema as z, defineCustomTool, CustomToolContext } from "@roo-code/types"
|
||||
// @ts-ignore - Node built-ins
|
||||
import { readdirSync, readFileSync, statSync } from "fs"
|
||||
// @ts-ignore - Node built-ins
|
||||
import { join, resolve } from "path"
|
||||
|
||||
export default defineCustomTool({
|
||||
name: "surefire_failures_summary",
|
||||
description: "Find Maven surefire/failsafe XML reports and return a structured summary of ONLY the failed/errored tests. Saves reading dozens of XML files manually.",
|
||||
parameters: z.object({
|
||||
projectRoot: z.string().describe("Repo root or module path (absolute or relative)"),
|
||||
module: z.string().optional().describe("Sub-path like 'java/modules/cs-modules/eau' to scope the search"),
|
||||
}),
|
||||
async execute({ projectRoot, module }, context: CustomToolContext) {
|
||||
try {
|
||||
// @ts-ignore - installed dependency
|
||||
const { XMLParser } = require("fast-xml-parser")
|
||||
|
||||
const root = resolve(module ? join(projectRoot, module) : projectRoot)
|
||||
const reportFiles: string[] = []
|
||||
|
||||
// Recursive walk to find surefire report XMLs
|
||||
const walk = (dir: string, depth: number) => {
|
||||
if (depth > 12) return
|
||||
let entries: string[]
|
||||
try {
|
||||
entries = readdirSync(dir)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
for (const entry of entries) {
|
||||
if (entry === "node_modules" || entry === ".git") continue
|
||||
const full = join(dir, entry)
|
||||
try {
|
||||
const st = statSync(full)
|
||||
if (st.isDirectory()) {
|
||||
if (entry === "surefire-reports" || entry === "failsafe-reports") {
|
||||
// Collect TEST-*.xml from this directory
|
||||
const xmls = readdirSync(full).filter(
|
||||
(f: string) => f.startsWith("TEST-") && f.endsWith(".xml")
|
||||
)
|
||||
for (const xml of xmls) {
|
||||
reportFiles.push(join(full, xml))
|
||||
}
|
||||
} else {
|
||||
walk(full, depth + 1)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// skip unreadable
|
||||
}
|
||||
}
|
||||
}
|
||||
walk(root, 0)
|
||||
|
||||
if (reportFiles.length === 0) {
|
||||
return JSON.stringify({
|
||||
reportsFound: 0,
|
||||
totalTests: 0,
|
||||
failed: 0,
|
||||
errors: 0,
|
||||
skipped: 0,
|
||||
failures: [],
|
||||
note: "No surefire/failsafe report XMLs found. Run 'mvn test' first.",
|
||||
}, null, 2)
|
||||
}
|
||||
|
||||
// Filter to only recent reports (within 1 hour of the newest)
|
||||
const mtimes = reportFiles.map((f) => {
|
||||
try { return statSync(f).mtimeMs } catch { return 0 }
|
||||
})
|
||||
const newest = Math.max(...mtimes)
|
||||
const cutoff = newest - 3600_000 // 1 hour
|
||||
|
||||
const parser = new XMLParser({
|
||||
ignoreAttributes: false,
|
||||
attributeNamePrefix: "@_",
|
||||
isArray: (name: string) => name === "testcase",
|
||||
})
|
||||
|
||||
let totalTests = 0
|
||||
let totalFailed = 0
|
||||
let totalErrors = 0
|
||||
let totalSkipped = 0
|
||||
let reportsProcessed = 0
|
||||
const failures: any[] = []
|
||||
|
||||
for (let i = 0; i < reportFiles.length; i++) {
|
||||
if (mtimes[i] < cutoff) continue
|
||||
reportsProcessed++
|
||||
|
||||
const xml = readFileSync(reportFiles[i], "utf-8")
|
||||
let doc: any
|
||||
try {
|
||||
doc = parser.parse(xml)
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
|
||||
const suite = doc.testsuite
|
||||
if (!suite) continue
|
||||
|
||||
const tests = parseInt(suite["@_tests"] || "0", 10)
|
||||
const failed = parseInt(suite["@_failures"] || "0", 10)
|
||||
const errors = parseInt(suite["@_errors"] || "0", 10)
|
||||
const skipped = parseInt(suite["@_skipped"] || "0", 10)
|
||||
|
||||
totalTests += tests
|
||||
totalFailed += failed
|
||||
totalErrors += errors
|
||||
totalSkipped += skipped
|
||||
|
||||
if ((failed + errors) > 0 && suite.testcase) {
|
||||
for (const tc of suite.testcase) {
|
||||
const failure = tc.failure || tc.error
|
||||
if (!failure) continue
|
||||
const msg = typeof failure === "string" ? failure : (failure["#text"] || failure["@_message"] || "")
|
||||
const type = failure["@_type"] || "Unknown"
|
||||
const lines = msg.split("\n").filter((l: string) => l.trim())
|
||||
failures.push({
|
||||
testClass: tc["@_classname"] || suite["@_name"] || "",
|
||||
testName: tc["@_name"] || "",
|
||||
type,
|
||||
messageFirstLine: lines[0]?.substring(0, 200) || "",
|
||||
stackFirstLine: lines[1]?.substring(0, 200) || "",
|
||||
file: reportFiles[i],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify({
|
||||
reportsFound: reportsProcessed,
|
||||
totalTests,
|
||||
failed: totalFailed,
|
||||
errors: totalErrors,
|
||||
skipped: totalSkipped,
|
||||
failures: failures.slice(0, 50), // cap at 50 to prevent token explosion
|
||||
}, null, 2)
|
||||
} catch (err: any) {
|
||||
return JSON.stringify({ error: err.message ?? String(err) }, null, 2)
|
||||
}
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user