Commit ea3d12d2 authored by Kelvin Fichter's avatar Kelvin Fichter

fix(ctb): clean and fix test names script

Cleans up the test names script and fixes a few errors.
parent 25be16bf
import fs from 'fs' import fs from 'fs'
import path from 'path'
type Check = { import { glob } from 'glob'
check: (name: string, parts: string[]) => void
makeError: (name: string) => Error
}
/** /**
* Function name checks * Series of function name checks.
*/ */
const FunctionNameChecks: { [name: string]: Check } = { const checks: Array<{
invalidCase: { check: (parts: string[]) => boolean
check: (name: string, parts: string[]): void => { error: string
parts.forEach((part) => { }> = [
if (part[0] !== part[0].toLowerCase()) { {
throw FunctionNameChecks.invalidCase.makeError(name) error: 'test name parts should be in camelCase',
} check: (parts: string[]): boolean => {
return parts.every((part) => {
return part[0] === part[0].toLowerCase()
}) })
}, },
makeError: (name: string): Error =>
new Error(
`Invalid test name "${name}".\n Test name parts should be in camelCase.`
),
}, },
invalidNumParts: { {
check: (name: string, parts: string[]): void => { error:
if (parts.length < 3 || parts.length > 4) { 'test names should have either 3 or 4 parts, each separated by underscores',
throw FunctionNameChecks.invalidNumParts.makeError(name) check: (parts: string[]): boolean => {
} return parts.length === 3 || parts.length === 4
}, },
makeError: (name: string): Error =>
new Error(
`Invalid test name "${name}".\n Test names should have either 3 or 4 parts, each separated by underscores.`
),
}, },
invalidPrefix: { {
check: (name: string, parts: string[]): void => { error: 'test names should begin with "test", "testFuzz", or "testDiff"',
if (!['test', 'testFuzz', 'testDiff'].includes(parts[0])) { check: (parts: string[]): boolean => {
throw FunctionNameChecks.invalidPrefix.makeError(name) return ['test', 'testFuzz', 'testDiff'].includes(parts[0])
}
}, },
makeError: (name: string): Error =>
new Error(
`Invalid test name "${name}".\n Names should begin with "test", "testFuzz", or "testDiff".`
),
}, },
invalidTestResult: { {
check: (name: string, parts: string[]): void => { error:
if ( 'test names should end with either "succeeds", "reverts", "fails", "works" or "benchmark[_num]"',
!['succeeds', 'reverts', 'fails', 'benchmark', 'works'].includes( check: (parts: string[]): boolean => {
return (
['succeeds', 'reverts', 'fails', 'benchmark', 'works'].includes(
parts[parts.length - 1] parts[parts.length - 1]
) && ) ||
parts[parts.length - 2] !== 'benchmark' (parts[parts.length - 2] === 'benchmark' &&
) { !isNaN(parseInt(parts[parts.length - 1], 10)))
throw FunctionNameChecks.invalidTestResult.makeError(name) )
}
}, },
makeError: (name: string): Error =>
new Error(
`Invalid test name "${name}".\n Test names should end with either "succeeds", "reverts", "fails", "works" or "benchmark[_num]".`
),
}, },
noFailureReason: { {
check: (name: string, parts: string[]): void => { error:
if ( 'failure tests should have 4 parts, third part should indicate the reason for failure',
['reverts', 'fails'].includes(parts[parts.length - 1]) && check: (parts: string[]): boolean => {
parts.length < 4 return (
) { parts.length === 4 ||
throw FunctionNameChecks.noFailureReason.makeError(name) !['reverts', 'fails'].includes(parts[parts.length - 1])
} )
}, },
makeError: (name: string): Error =>
new Error(
`Invalid test name "${name}".\n Failure tests should have 4 parts. The third part should indicate the reason for failure.`
),
}, },
} ]
// Given a test function name, ensures it matches the expected format
const handleFunctionName = (name: string) => {
if (!name.startsWith('test')) {
return
}
const parts = name.split('_')
Object.values(FunctionNameChecks).forEach(({ check }) => check(name, parts))
}
/**
* Script for checking that all test functions are named correctly.
*/
const main = async () => { const main = async () => {
const artifactsPath = './forge-artifacts' const errors: string[] = []
const files = glob.sync('./forge-artifacts/**/*.t.sol/*Test*.json')
// Get a list of all solidity files with the extension t.sol for (const file of files) {
const solTestFiles = fs const artifact = JSON.parse(fs.readFileSync(file, 'utf8'))
.readdirSync(artifactsPath) for (const element of artifact.abi) {
.filter((solFile) => solFile.includes('.t.sol')) // Skip non-functions and functions that don't start with "test".
if (element.type !== 'function' || !element.name.startsWith('test')) {
continue
}
// Build a list of artifacts for contracts which include the string Test in them // Check the rest.
let testArtifacts: string[] = [] for (const { check, error } of checks) {
for (const file of solTestFiles) { if (!check(element.name.split('_'))) {
testArtifacts = testArtifacts.concat( errors.push(`in ${file} function ${element.name}: ${error}`)
fs }
.readdirSync(path.join(artifactsPath, file)) }
.filter((x) => x.includes('Test')) }
.map((x) => path.join(artifactsPath, stf, x))
)
} }
for (const artifact of testArtifacts) { if (errors) {
JSON.parse(fs.readFileSync(artifact, 'utf8')) console.error(errors.join('\n'))
.abi.filter((el) => el.type === 'function') process.exit(1)
.forEach((el) => {
try {
handleFunctionName(el.name)
} catch (error) {
throw new Error(`In ${path.parse(artifact).name}: ${error.message}`)
}
})
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment