From 4d9731efbee16089892422bd24dc39044ed9e482 Mon Sep 17 00:00:00 2001 From: Mike Rudenko Date: Tue, 23 Sep 2025 21:35:38 +0300 Subject: [PATCH] test: add unit tests for getPropertyAndSequenceString function --- src/medusa/index.ts | 17 +++---- src/medusa/medusa.test.ts | 103 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 9 deletions(-) create mode 100644 src/medusa/medusa.test.ts diff --git a/src/medusa/index.ts b/src/medusa/index.ts index 4ad2f8d..79a039d 100644 --- a/src/medusa/index.ts +++ b/src/medusa/index.ts @@ -32,16 +32,14 @@ export function processMedusa(line: string, jobStats: FuzzingResults): void { jobStats.duration = captureFuzzingDuration(line.replace("fuzz: elapsed:", "")) ?? ""; // TODO 0XSI - fix this const coverageMatch = line.match(/coverage: (\d+)/); - const numberOfTestsMatch = line.match( /calls:\s(\d+)/); + const numberOfTestsMatch = line.match(/calls:\s(\d+)/); if (coverageMatch) { jobStats.coverage = +coverageMatch[1]; } - if (numberOfTestsMatch) { jobStats.numberOfTests = parseInt(numberOfTestsMatch[1]); } - } else if (line.includes("Test summary:")) { const passedMatch = line.match(/(\d+ test\(s\) passed)/); const failedMatch = line.match(/(\d+ test\(s\) failed)/); @@ -141,7 +139,7 @@ export function getPropertyAndSequenceString( const bodies = splitted.map((entry) => vmData ? getFunctionCallsWithVM(entry, vmData) - : getFunctionCalls(entry).map((body) => body.replace(" (block=", ";")) + : getFunctionCalls(entry)?.map((body) => body.replace(" (block=", ";")) ); const headers = splitted.map((entry, counter) => getHeaders(entry, counter)); if (bodies.length != headers.length) { @@ -172,7 +170,8 @@ export function getFunctionCallsWithVM( logs: string, vmData?: VmParsingData ): string[] { - const pattern: RegExp =/(?<=\.)[\w]+\(([^()]*(?:\([^()]*\)[^()]*)*)\)\(?([^()]*)\)?\s+\(block=\d*,\s*time=\d*,\s*gas=\d*,\s*gasprice=\d*,\s*value=\d*,\s*sender=0x[0-9a-fA-F]{1,40}\)/gm + const pattern: RegExp = + /(?<=\.)[\w]+\(([^()]*(?:\([^()]*\)[^()]*)*)\)\(?([^()]*)\)?\s+\(block=\d*,\s*time=\d*,\s*gas=\d*,\s*gasprice=\d*,\s*value=\d*,\s*sender=0x[0-9a-fA-F]{1,40}\)/gm; const matches: RegExpMatchArray | null = logs.match(pattern); const functionCalls = matches?.map((entry) => { let returnData = ""; @@ -188,16 +187,16 @@ export function getFunctionCallsWithVM( // Check for uncommon scenarios like: ((hex"address",uint256)[])([]) if (cleanedData.includes("((")) { // Remove the content inside the first set of parentheses and replace it with an empty string - cleanedData = cleanedData.replace(patternArrayParams, ''); + cleanedData = cleanedData.replace(patternArrayParams, ""); // Remove the extra '([])' from the second set of parentheses - cleanedData = cleanedData.replace(emptyArrayPattern, ''); + cleanedData = cleanedData.replace(emptyArrayPattern, ""); } else if (cleanedData.includes("()()")) { // For common cases like: check_liquidation_solvency()(); cleanedData = cleanedData.replace("()()", "()"); } else if (/\([^\(\)]*\)\([^\(\)]*\)/.test(cleanedData)) { // If there are two sets of parentheses, remove the first set and its contents - cleanedData = cleanedData.replace(/\([^\(\)]*\)(?=\([^\(\)]*\))/, ''); + cleanedData = cleanedData.replace(/\([^\(\)]*\)(?=\([^\(\)]*\))/, ""); } if (vmData) { @@ -244,7 +243,7 @@ export function getFunctionCalls(logs: string): string[] { const pattern: RegExp = /\b(\w+)\(([^)]*)\)\s+\(block=/gm; const matches: RegExpMatchArray | null = logs.match(pattern); - const functionCalls = matches?.map((entry) => entry.toString()) as string[]; + const functionCalls = matches?.map((entry) => entry.toString()) || []; return functionCalls; } diff --git a/src/medusa/medusa.test.ts b/src/medusa/medusa.test.ts new file mode 100644 index 0000000..aa142e8 --- /dev/null +++ b/src/medusa/medusa.test.ts @@ -0,0 +1,103 @@ +import { getPropertyAndSequenceString } from "./index"; + +describe("Medusa Parser", () => { + describe("getPropertyAndSequenceString", () => { + it("should handle logs with no function calls without crashing", () => { + const logsWithNoFunctionCalls = ` +[FAILED] Assertion Test: CryticTester.restakingRouter_redeem(address,uint256) +Test for method "CryticTester.restakingRouter_redeem(address,uint256)" resulted in an assertion failure after the following call sequence: +[Call Sequence] +1) CryticTester.restakingBondMM_unpause()() (block=68924, time=621386, gas=12500000, gasprice=1, value=0, sender=0x30000) +2) CryticTester.restakingRouter_redeem(address,uint256)(0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496, 1000309797381228308400539929892) (block=68924, time=621386, gas=12500000, gasprice=1, value=0, sender=0x30000) +[Execution Trace] + => [call] CryticTester.restakingRouter_redeem(address,uint256)(0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496, 1000309797381228308400539929892) (addr=0x2e234DAe75C793f67A35089C9d99245E1C58470b, value=0, sender=0x30000) + => [panic: assertion failed] +[Logs] + "loanData[_maturity].l", 0 + "cashOut", 0 + `; + + // This should not crash + expect(() => { + const result = getPropertyAndSequenceString(logsWithNoFunctionCalls); + expect(result).toBeDefined(); + expect(Array.isArray(result)).toBe(true); + }).not.toThrow(); + }); + + it("should handle logs with function calls correctly", () => { + const logsWithFunctionCalls = ` +[FAILED] Assertion Test: CryticTester.restakingRouter_redeem(address,uint256) +Test for method "CryticTester.restakingRouter_redeem(address,uint256)" resulted in an assertion failure after the following call sequence: +[Call Sequence] +1) CryticTester.restakingBondMM_unpause()() (block=68924, time=621386, gas=12500000, gasprice=1, value=0, sender=0x30000) +2) CryticTester.restakingRouter_redeem(address,uint256)(0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496, 1000309797381228308400539929892) (block=68924, time=621386, gas=12500000, gasprice=1, value=0, sender=0x30000) + `; + + const result = getPropertyAndSequenceString(logsWithFunctionCalls); + expect(result).toBeDefined(); + expect(Array.isArray(result)).toBe(true); + expect(result.length).toBeGreaterThan(0); + + if (result.length > 0) { + expect(result[0]).toHaveProperty("brokenProperty"); + expect(result[0]).toHaveProperty("sequence"); + expect(result[0].brokenProperty).toBe("restakingRouter_redeem"); + } + }); + + it("should handle empty logs without crashing", () => { + const emptyLogs = ""; + + expect(() => { + const result = getPropertyAndSequenceString(emptyLogs); + expect(result).toBeDefined(); + expect(Array.isArray(result)).toBe(true); + expect(result.length).toBe(0); + }).not.toThrow(); + }); + + it("should handle logs with no [FAILED] sections", () => { + const logsWithoutFailed = ` +Some random log content +without any failed sections + `; + + expect(() => { + const result = getPropertyAndSequenceString(logsWithoutFailed); + expect(result).toBeDefined(); + expect(Array.isArray(result)).toBe(true); + // The function may return some results even without [FAILED] sections + // The important thing is that it doesn't crash + }).not.toThrow(); + }); + + it("should handle the actual failed logs from the file", () => { + // This is a simplified version of the actual failed logs that were causing the crash + const actualFailedLogs = ` +[FAILED] Assertion Test: CryticTester.restakingRouter_redeem(address,uint256) +Test for method "CryticTester.restakingRouter_redeem(address,uint256)" resulted in an assertion failure after the following call sequence: +[Call Sequence] +1) CryticTester.restakingBondMM_unpause()() (block=68924, time=621386, gas=12500000, gasprice=1, value=0, sender=0x30000) +2) CryticTester.restakingRouter_redeem(address,uint256)(0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496, 1000309797381228308400539929892) (block=68924, time=621386, gas=12500000, gasprice=1, value=0, sender=0x30000) +[Execution Trace] + => [call] CryticTester.restakingRouter_redeem(address,uint256)(0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496, 1000309797381228308400539929892) (addr=0x2e234DAe75C793f67A35089C9d99245E1C58470b, value=0, sender=0x30000) + => [call] RestakingRouter.getRate(address)(0xcDAA741ad6c9B73a249718651B6ff86565beC634) (addr=0x6985c91901D47804564bB155267991BF3Fd9f476, value=, sender=0x2e234DAe75C793f67A35089C9d99245E1C58470b) + => [call] RestakingBondMM.getUintRate()() (addr=0xcDAA741ad6c9B73a249718651B6ff86565beC634, value=, sender=0x6985c91901D47804564bB155267991BF3Fd9f476) + => [return (0, 50000000000000000)] + => [return (0, 50000000000000000)] + => [panic: assertion failed] +[Logs] + "loanData[_maturity].l", 0 + "cashOut", 0 + `; + + // This should not crash with the fix + expect(() => { + const result = getPropertyAndSequenceString(actualFailedLogs); + expect(result).toBeDefined(); + expect(Array.isArray(result)).toBe(true); + }).not.toThrow(); + }); + }); +});