1

I want to give score to several python scripts written by different people and i want to automate the answer check since the given question is same. So we send an input to another python file, we get the output (the terminal/console output) then we compare it, all that within a python file (Like hackerrank, uri, or another competitive programming website)

For example the problem is to multiply the given input by 2. Then i have one python script answer_check.py to automate answer checking, and i have another python script which is one of the answer a.py.

a.py:

a= int(input('input a: '))
res= a*2

print(res)

answer_check.py:

# Some code to send input to a.py

# Then some code to get the console output from a given input

if given_output==desired_output:
    score= 100

What i have tried:

  • I have read some other stackoverflow post that related to this problem but it is kinda different because either they don't have input() in the answer file they want to check, or they do input via sys.args .
  • I have tried pexpect but but apparently it doesn't apply to windows os
  • I have tried wexpect it is like pexpect but for windows, but i have an installation problem with pywin32
  • I tried runpy but we have to input manually
  • I tried subprocess module
from subprocess import Popen, PIPE

p = Popen("python a.py", stdin=PIPE, stdout=PIPE, shell=False)
out = p.communicate(input='1', timeout=5)
print(out)

But it give me this error

  File "a.py", line 1, in <module>
    a= input('input a: ')
EOFError: EOF when reading a line
Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='cp1252'>
OSError: [Errno 22] Invalid argument

If you know please answer even though it is on another language :)

Yusuf Syam
  • 701
  • 1
  • 4
  • 18

4 Answers4

1

subprocess.Popen.communicate docs claims that

Interact with process: Send data to stdin. Read data from stdout and stderr, until end-of-file is reached. Wait for process to terminate and set the returncode attribute. The optional input argument should be data to be sent to the child process, or None, if no data should be sent to the child. If streams were opened in text mode, input must be a string. Otherwise, it must be bytes.

So you should provide bytes, not str, that is your example should be altered to

from subprocess import Popen, PIPE

p = Popen("python a.py", stdin=PIPE, stdout=PIPE, shell=False)
out = p.communicate(input=b'1', timeout=5)
print(out)

If you need to prepare input from str use .encode() for example

from subprocess import Popen, PIPE
myinput = '1'
p = Popen("python a.py", stdin=PIPE, stdout=PIPE, shell=False)
out = p.communicate(input=myinput.encode(), timeout=5)
print(out)
Daweo
  • 31,313
  • 3
  • 12
  • 25
  • Thanks for the answer! it works fine for me. But i have one more problem, what if `a.py` have more input and output? how to send and get them? – Yusuf Syam Sep 26 '22 at 11:13
  • Oh i get it already, we have to separate the input on `myinput` by new line – Yusuf Syam Sep 26 '22 at 11:21
  • As an aside, like the documentation also tells you, you want to avoid `Popen` in favor of `check_output` and friends, which encapsulate this into a one-liner. – tripleee Sep 26 '22 at 11:28
  • `shell=False` is incorrect here, or rather, it only works on Windows. You want `subprocess.check_output(["python", "a.py"], text=True, timeout=5)` – tripleee Sep 26 '22 at 11:29
1

A much better design is to refactor the code to not require interactive I/O.

a.py:

def make_res(a):
    return a*2

def main():
    a = input('input a: ')
    res = make_res(a) 
    print(res)
    
if __name__ == "__main__":
    main()

answer_check.py:

from .a import make_res

if make_res(value) == desired_output:
    score = 100
tripleee
  • 175,061
  • 34
  • 275
  • 318
1

After a bit of testing myself, you could use subprocess to invoke the script to be tested from the test script and then import the script being tested as a module and call the variables. I set it up like this

import importlib
import subprocess
script='a'
subprocess.call(script+'.py', shell=True)
script=importlib.import_module(script, package=None)
a=script.a
res=script.res
if a*2 == res:
    score=100

And then the tested script needs a small correction to make the input actually be a number, so just put int() around the input like this

a= int(input('input a: '))
res= a*2
Dylan Hatz
  • 11
  • 4
  • It works but we still have to input manually, thanks for the correction – Yusuf Syam Sep 26 '22 at 11:18
  • What do you mean "we still have to input manually"? If you mean that the script name needs to be typed by hand, it could be called from a list and this whole thing could be in a for loop, script is a variable so that it could be brought in from somewhere else. You could make a list of all of the scripts to test and then call "for script in list" and just go through all of them – Dylan Hatz Sep 26 '22 at 11:20
  • I mean the variable `a` in `a.py` not the script name – Yusuf Syam Sep 26 '22 at 11:22
  • Oh, then that would just mean that the outcome would be wrong, because whatever you type as input becomes a string, so if you put in 10 as "a", then it would be "1010" instead of 20 because multiplying strings just makes them repeat, since 10 and "10" are not the same. You can only get the number form of the input by converting it – Dylan Hatz Sep 26 '22 at 11:35
1

Missing: timeout=None

import pexpect

child = pexpect.spawn('python3 test.py')
fout = open('mylog.txt','wb')
child.logfile = fout
child.expect('input a: ',  timeout=None)
child.sendline('2')
child.expect('22', timeout=None)
A. Herlas
  • 173
  • 9