github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/scripts/checks/check-test-names.ts (about) 1 import fs from 'fs' 2 import path from 'path' 3 import { execSync } from 'child_process' 4 5 type Check = (parts: string[]) => boolean 6 type Checks = Array<{ 7 check: Check 8 error: string 9 }> 10 11 /** 12 * Series of function name checks. 13 */ 14 const checks: Checks = [ 15 { 16 error: 'test name parts should be in camelCase', 17 check: (parts: string[]): boolean => { 18 return parts.every((part) => { 19 return part[0] === part[0].toLowerCase() 20 }) 21 }, 22 }, 23 { 24 error: 25 'test names should have either 3 or 4 parts, each separated by underscores', 26 check: (parts: string[]): boolean => { 27 return parts.length === 3 || parts.length === 4 28 }, 29 }, 30 { 31 error: 'test names should begin with "test", "testFuzz", or "testDiff"', 32 check: (parts: string[]): boolean => { 33 return ['test', 'testFuzz', 'testDiff'].includes(parts[0]) 34 }, 35 }, 36 { 37 error: 38 'test names should end with either "succeeds", "reverts", "fails", "works" or "benchmark[_num]"', 39 check: (parts: string[]): boolean => { 40 return ( 41 ['succeeds', 'reverts', 'fails', 'benchmark', 'works'].includes( 42 parts[parts.length - 1] 43 ) || 44 (parts[parts.length - 2] === 'benchmark' && 45 !isNaN(parseInt(parts[parts.length - 1], 10))) 46 ) 47 }, 48 }, 49 { 50 error: 51 'failure tests should have 4 parts, third part should indicate the reason for failure', 52 check: (parts: string[]): boolean => { 53 return ( 54 parts.length === 4 || 55 !['reverts', 'fails'].includes(parts[parts.length - 1]) 56 ) 57 }, 58 }, 59 ] 60 61 /** 62 * Script for checking that all test functions are named correctly. 63 */ 64 const main = async () => { 65 const result = execSync('forge config --json') 66 const config = JSON.parse(result.toString()) 67 const out = config.out || 'out' 68 69 const paths = [] 70 71 const readFilesRecursively = (dir: string) => { 72 const files = fs.readdirSync(dir) 73 74 for (const file of files) { 75 const filePath = path.join(dir, file) 76 const fileStat = fs.statSync(filePath) 77 78 if (fileStat.isDirectory()) { 79 readFilesRecursively(filePath) 80 } else { 81 paths.push(filePath) 82 } 83 } 84 } 85 86 readFilesRecursively(out) 87 88 console.log('Success:') 89 const errors: string[] = [] 90 91 for (const filepath of paths) { 92 const artifact = JSON.parse(fs.readFileSync(filepath, 'utf8')) 93 94 let isTest = false 95 for (const element of artifact.abi) { 96 if (element.name === 'IS_TEST') { 97 isTest = true 98 break 99 } 100 } 101 102 if (isTest) { 103 let success = true 104 for (const element of artifact.abi) { 105 // Skip non-functions and functions that don't start with "test". 106 if (element.type !== 'function' || !element.name.startsWith('test')) { 107 continue 108 } 109 110 // Check the rest. 111 for (const { check, error } of checks) { 112 if (!check(element.name.split('_'))) { 113 errors.push(`${filepath}#${element.name}: ${error}`) 114 success = false 115 } 116 } 117 } 118 if (success) { 119 console.log(` - ${path.parse(filepath).name}`) 120 } 121 } 122 } 123 124 if (errors.length > 0) { 125 console.error(errors.join('\n')) 126 process.exit(1) 127 } 128 } 129 130 main()