Files
littlefs/scripts/explode_asserts.py
Christopher Haster fb65057a3c Restructured block devices again for better test exploitation
Also finished migrating tests with test_relocations and test_exhaustion.

The issue I was running into when migrating these tests was a lack of
flexibility with what you could do with the block devices. It was
possible to hack in some hooks for things like bad blocks and power
loss, but it wasn't clean or easily extendable.

The solution here was to just put all of these test extensions into a
third block device, testbd, that uses the other two example block
devices internally.

testbd has several useful features for testing. Note this makes it a
pretty terrible block device _example_ since these hooks look more
complicated than a block device needs to be.

- testbd can simulate different erase values, supporting 1s, 0s, other byte
  patterns, or no erases at all (which can cause surprising bugs). This
  actually depends on the simulated erase values in ramdb and filebd.

  I did try to move this out of rambd/filebd, but it's not possible to
  simulate erases in testbd without buffering entire blocks and creating
  an excessive amount of extra write operations.

- testbd also helps simulate power-loss by containing a "power cycles"
  counter that is decremented every write operation until it calls exit.

  This is notably faster than the previous gdb approach, which is
  valuable since the reentrant tests tend to take a while to resolve.

- testbd also tracks wear, which can be manually set and read. This is
  very useful for testing things like bad block handling, wear leveling,
  or even changing the effective size of the block device at runtime.
2020-01-20 19:27:24 -06:00

216 lines
6.3 KiB
Python
Executable File

#!/usr/bin/env python3
import parsy as p
import re
import io
import sys
ASSERT_PATTERN = p.string('LFS_ASSERT') | p.string('assert')
ASSERT_CHARS = 'La'
ASSERT_TARGET = '__LFS_ASSERT_{TYPE}_{COMP}'
ASSERT_TESTS = {
'int': """
__typeof__({lh}) _lh = {lh};
__typeof__({lh}) _rh = (__typeof__({lh})){rh};
if (!(_lh {op} _rh)) {{
printf("%s:%d:assert: "
"assert failed with %"PRIiMAX", expected {comp} %"PRIiMAX"\\n",
{file}, {line}, (intmax_t)_lh, (intmax_t)_rh);
fflush(NULL);
raise(SIGABRT);
}}
""",
'str': """
const char *_lh = {lh};
const char *_rh = {rh};
if (!(strcmp(_lh, _rh) {op} 0)) {{
printf("%s:%d:assert: "
"assert failed with \\\"%s\\\", expected {comp} \\\"%s\\\"\\n",
{file}, {line}, _lh, _rh);
fflush(NULL);
raise(SIGABRT);
}}
""",
'bool': """
bool _lh = !!({lh});
bool _rh = !!({rh});
if (!(_lh {op} _rh)) {{
printf("%s:%d:assert: "
"assert failed with %s, expected {comp} %s\\n",
{file}, {line}, _lh ? "true" : "false", _rh ? "true" : "false");
fflush(NULL);
raise(SIGABRT);
}}
""",
}
def mkassert(lh, rh='true', type='bool', comp='eq'):
return ((ASSERT_TARGET + "({lh}, {rh}, __FILE__, __LINE__, __func__)")
.format(
type=type, TYPE=type.upper(),
comp=comp, COMP=comp.upper(),
lh=lh.strip(' '),
rh=rh.strip(' ')))
def mkdecl(type, comp, op):
return ((
"#define "+ASSERT_TARGET+"(lh, rh, file, line, func)"
" do {{"+re.sub('\s+', ' ', ASSERT_TESTS[type])+"}} while (0)\n")
.format(
type=type, TYPE=type.upper(),
comp=comp, COMP=comp.upper(),
lh='lh', rh='rh', op=op,
file='file', line='line', func='func'))
# add custom until combinator
def until(self, end):
return end.should_fail('should fail').then(self).many()
p.Parser.until = until
pcomp = (
p.string('==').tag('eq') |
p.string('!=').tag('ne') |
p.string('<=').tag('le') |
p.string('>=').tag('ge') |
p.string('<').tag('lt') |
p.string('>').tag('gt'));
plogic = p.string('&&') | p.string('||')
@p.generate
def pstrassert():
yield ASSERT_PATTERN + p.regex('\s*') + p.string('(') + p.regex('\s*')
yield p.string('strcmp') + p.regex('\s*') + p.string('(') + p.regex('\s*')
lh = yield pexpr.until(p.string(',') | p.string(')') | plogic)
yield p.string(',') + p.regex('\s*')
rh = yield pexpr.until(p.string(')') | plogic)
yield p.string(')') + p.regex('\s*')
op = yield pcomp
yield p.regex('\s*') + p.string('0') + p.regex('\s*') + p.string(')')
return mkassert(''.join(lh), ''.join(rh), 'str', op[0])
@p.generate
def pintassert():
yield ASSERT_PATTERN + p.regex('\s*') + p.string('(') + p.regex('\s*')
lh = yield pexpr.until(pcomp | p.string(')') | plogic)
op = yield pcomp
rh = yield pexpr.until(p.string(')') | plogic)
yield p.string(')')
return mkassert(''.join(lh), ''.join(rh), 'int', op[0])
@p.generate
def pboolassert():
yield ASSERT_PATTERN + p.regex('\s*') + p.string('(') + p.regex('\s*')
expr = yield pexpr.until(p.string(')'))
yield p.string(')')
return mkassert(''.join(expr), 'true', 'bool', 'eq')
passert = p.peek(ASSERT_PATTERN) >> (pstrassert | pintassert | pboolassert)
@p.generate
def pcomment1():
yield p.string('//')
s = yield p.regex('[^\\n]*')
yield p.string('\n')
return '//' + s + '\n'
@p.generate
def pcomment2():
yield p.string('/*')
s = yield p.regex('((?!\*/).)*')
yield p.string('*/')
return '/*' + ''.join(s) + '*/'
@p.generate
def pcomment3():
yield p.string('#')
s = yield p.regex('[^\\n]*')
yield p.string('\n')
return '#' + s + '\n'
pws = p.regex('\s+') | pcomment1 | pcomment2 | pcomment3
@p.generate
def pstring():
q = yield p.regex('["\']')
s = yield (p.string('\\%s' % q) | p.regex('[^%s]' % q)).many()
yield p.string(q)
return q + ''.join(s) + q
@p.generate
def pnested():
l = yield p.string('(')
n = yield pexpr.until(p.string(')'))
r = yield p.string(')')
return l + ''.join(n) + r
pexpr = (
# shortcut for a bit better performance
p.regex('[^%s/#\'"():;{}=><,&|-]+' % ASSERT_CHARS) |
pws |
passert |
pstring |
pnested |
p.string('->') |
p.regex('.', re.DOTALL))
@p.generate
def pstmt():
ws = yield pws.many()
lh = yield pexpr.until(p.string('=>') | p.regex('[:;{}]'))
op = yield p.string('=>').optional()
if op == '=>':
rh = yield pstmt
return ''.join(ws) + mkassert(''.join(lh), rh, 'int', 'eq')
else:
return ''.join(ws) + ''.join(lh)
@p.generate
def pstmts():
a = yield pstmt
b = yield (p.regex('[:;{}]') + pstmt).many()
return [a] + b
def main(args):
inf = open(args.input, 'r') if args.input else sys.stdin
outf = open(args.output, 'w') if args.output else sys.stdout
# parse C code
input = inf.read()
stmts = pstmts.parse(input)
# write extra verbose asserts
outf.write("#include <stdbool.h>\n")
outf.write("#include <stdint.h>\n")
outf.write("#include <inttypes.h>\n")
outf.write("#include <signal.h>\n")
outf.write(mkdecl('int', 'eq', '=='))
outf.write(mkdecl('int', 'ne', '!='))
outf.write(mkdecl('int', 'lt', '<'))
outf.write(mkdecl('int', 'gt', '>'))
outf.write(mkdecl('int', 'le', '<='))
outf.write(mkdecl('int', 'ge', '>='))
outf.write(mkdecl('str', 'eq', '=='))
outf.write(mkdecl('str', 'ne', '!='))
outf.write(mkdecl('str', 'lt', '<'))
outf.write(mkdecl('str', 'gt', '>'))
outf.write(mkdecl('str', 'le', '<='))
outf.write(mkdecl('str', 'ge', '>='))
outf.write(mkdecl('bool', 'eq', '=='))
if args.input:
outf.write("#line %d \"%s\"\n" % (1, args.input))
# write parsed statements
for stmt in stmts:
outf.write(stmt)
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(
description="Cpp step that increases assert verbosity")
parser.add_argument('input', nargs='?',
help="Input C file after cpp.")
parser.add_argument('-o', '--output',
help="Output C file.")
main(parser.parse_args())