1

I have a simple test bench implemented in python.

The test runner exposes a set of functions to the test. This may be considered as a domain specific language for writing tests. Most test cases form simple sequences of calls to these functions, sometimes involving control flow statements, but nothing more complicated than that.

Now a few test cases have become too complex and would benefit from being refactored using functions. This is where I run into problems. From within the functions I cannot access local symbols defined inside the test case. I can also not access the functions exposed by the runner (log and verify).

''' Testing variable scopes in exec()'''

PRG_1 = '''
a = 42
log('Testing a = ' + str(a))
verify(a, 42)
'''

PRG_2 = '''
a = 42
def f():
    c = a  # Error 'a' not defined
    log(c) # Error 'log' not defined

f()
'''

class TestRunner():
    def __init__(self, prg):
        self.prg = prg

    def log(self, msg):
        print(msg)

    def verify(self, a, b):
        print('PASSED' if a == b else 'FAILED')

    def run(self):
        # Bring methods into local scope to avoid having 'self.' in DSL
        log = self.log
        verify = self.verify

        # Execute test
        exec(self.prg)

r = TestRunner(PRG_1)
r.run()

r = TestRunner(PRG_2)
r.run()

Any idea on how I can get this to work? Maybe there is a different (and better way) to accomplish this, that I as a C++ developer do not see.

$ python3 test.py
Testing a = 42
PASSED
Traceback (most recent call last):
  File "test.py", line 42, in <module>
    r.run()
  File "test.py", line 35, in run
    exec(self.prg)
  File "<string>", line 7, in <module>
  File "<string>", line 4, in f
NameError: name 'a' is not defined
2
  • You can pass locals and globals to exec, see the documentation Commented Dec 1, 2017 at 11:58
  • I tried exec(self.prg, globals=globals(), locals=locals()) but got TypeError: exec() takes no keyword arguments. Then tried using positional arguments instead, but to no help. Commented Dec 1, 2017 at 12:10

1 Answer 1

2

If you do:

def run(self):
    exec(self.prg, {'log': self.log, 'verify': self.verify})

Produces:

42

Generally a good idea to avoid dynamic code execution like this anyway!

Sign up to request clarification or add additional context in comments.

5 Comments

Thank you. This may solve my problems. I added log and verify to scope. Not a as that is a test case local variable. Now my small example works. I did not use the wrapper function, but for some reason a is found anyway.
I understand the potential security issues of dynamic code execution, but in this case all persons writing test cases are trusted and might as well modify the actual test bench if they wanted to execute malicious code. Any other reasons to avoid dynamic execution?
Security was the reason I had in mind. Beyond that I guess things like code readability, IDE integration, syntax highlighting come to mind.
Ok. We store the test cases in files and not in string literals so we get the coloring. But it not very nice to get syntactic errors as <string>:57 instead of a filename. Also debugging is limited to printf-debugging.
Simplified my answer, works perfectly well in Python 2, I was just being an idiot.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.