Skip to content

[check50_assert] Bad tokenizing logic causes check50_assert to run functions more than once. #387

@ivanharvard

Description

@ivanharvard
#ENABLE_CHECK50_ASSERT=true
import check50
import check50.c

@check50.check()
def exists():
    """test.c exists"""
    check50.exists('test.c')

@check50.check(exists)
def compiles():
    """test.c compiles"""
    check50.c.compile('test.c', lcs50=True)

@check50.check()
def foo():
    """abc"""
    assert check50.run("pwd").stdout() == "foo"

Testing on this file returns this output:

Results for . generated by check50 v4.0.0.dev0
:) test.c exists
    checking that test.c exists...
:) test.c compiles
    running clang test.c -o test -std=c11 -ggdb -lm -lcs50...
:( abc
    expected: "'foo'"
    actual:   "/tmp/tmpu97pc8q4/foo\n"
    checked: check50.run('pwd').stdout() == 'foo'
    where check50.run('pwd').stdout() = '/tmp/tmpu97pc8q4/foo\n'
    running pwd...
    running pwd...

pwd should only ever run once. What happens in this case is caused by this pair of lines in runtime.py:

context[expr_str] = eval(expr_str, caller_globals, caller_locals)
cond = eval(eval_src, eval_globals, eval_locals)

The first of the two lines is supposed to evaluate the expression and store its value within context. After we've replaced the evaluated statements in our src conditional and stored that in eval_src, we evaluate eval_src in the second of the of the two lines.

If we end up running a command twice, that is because we've improperly replaced the evaluated statements in src (see the line below), causing eval(eval_src, ...) to evaluate again.

eval_src, eval_context = substitute_expressions(src, filtered_context)

So substitute_expressions must have an issue with its tokenizing logic.

Metadata

Metadata

Assignees

Labels

4.xIssues relating to check50 4.xbug

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions