forge-test-names.ts 3.12 KB
Newer Older
1
import fs from 'fs'
2 3
import path from 'path'
import { execSync } from 'child_process'
4

5
/**
6
 * Series of function name checks.
7
 */
8 9 10 11
const checks: Array<{
  check: (parts: string[]) => boolean
  error: string
}> = [
Mark Tyneway's avatar
Mark Tyneway committed
12 13 14 15 16 17
  {
    error: 'test name parts should be in camelCase',
    check: (parts: string[]): boolean => {
      return parts.every((part) => {
        return part[0] === part[0].toLowerCase()
      })
18
    },
Mark Tyneway's avatar
Mark Tyneway committed
19 20 21 22 23 24
  },
  {
    error:
      'test names should have either 3 or 4 parts, each separated by underscores',
    check: (parts: string[]): boolean => {
      return parts.length === 3 || parts.length === 4
25
    },
Mark Tyneway's avatar
Mark Tyneway committed
26 27 28 29 30
  },
  {
    error: 'test names should begin with "test", "testFuzz", or "testDiff"',
    check: (parts: string[]): boolean => {
      return ['test', 'testFuzz', 'testDiff'].includes(parts[0])
31
    },
Mark Tyneway's avatar
Mark Tyneway committed
32 33 34 35 36 37 38 39 40 41 42 43
  },
  {
    error:
      'test names should end with either "succeeds", "reverts", "fails", "works" or "benchmark[_num]"',
    check: (parts: string[]): boolean => {
      return (
        ['succeeds', 'reverts', 'fails', 'benchmark', 'works'].includes(
          parts[parts.length - 1]
        ) ||
        (parts[parts.length - 2] === 'benchmark' &&
          !isNaN(parseInt(parts[parts.length - 1], 10)))
      )
44
    },
Mark Tyneway's avatar
Mark Tyneway committed
45 46 47 48 49 50 51 52 53
  },
  {
    error:
      'failure tests should have 4 parts, third part should indicate the reason for failure',
    check: (parts: string[]): boolean => {
      return (
        parts.length === 4 ||
        !['reverts', 'fails'].includes(parts[parts.length - 1])
      )
54
    },
Mark Tyneway's avatar
Mark Tyneway committed
55 56
  },
]
57

58 59 60
/**
 * Script for checking that all test functions are named correctly.
 */
61
const main = async () => {
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
  const result = execSync('forge config --json')
  const config = JSON.parse(result.toString())
  const out = config.out || 'out'

  const paths = []

  const readFilesRecursively = (dir: string) => {
    const files = fs.readdirSync(dir)

    for (const file of files) {
      const filePath = path.join(dir, file)
      const fileStat = fs.statSync(filePath)

      if (fileStat.isDirectory()) {
        readFilesRecursively(filePath)
      } else {
        paths.push(filePath)
      }
    }
  }

  readFilesRecursively(out)

  console.log('Success:')
86
  const errors: string[] = []
87 88 89 90 91

  for (const filepath of paths) {
    const artifact = JSON.parse(fs.readFileSync(filepath, 'utf8'))

    let isTest = false
92
    for (const element of artifact.abi) {
93 94 95
      if (element.name === 'IS_TEST') {
        isTest = true
        break
96
      }
97 98 99 100 101 102 103 104 105
    }

    if (isTest) {
      let success = true
      for (const element of artifact.abi) {
        // Skip non-functions and functions that don't start with "test".
        if (element.type !== 'function' || !element.name.startsWith('test')) {
          continue
        }
106

107 108 109
        // Check the rest.
        for (const { check, error } of checks) {
          if (!check(element.name.split('_'))) {
110
            errors.push(`${filepath}#${element.name}: ${error}`)
111 112
            success = false
          }
113 114
        }
      }
115 116 117
      if (success) {
        console.log(` - ${path.parse(filepath).name}`)
      }
118
    }
119 120
  }

121
  if (errors.length > 0) {
122
    console.error(errors.join('\n'))
123
    process.exit(1)
124 125
  }
}
126 127

main()