示例#1
0
 def test_bad_arg(self):
     p = PopenSpawn("cat")
     with self.assertRaisesRegexp(TypeError, ".*must be one of"):
         p.expect(1)
     with self.assertRaisesRegexp(TypeError, ".*must be one of"):
         p.expect([1, b"2"])
     with self.assertRaisesRegexp(TypeError, ".*must be one of"):
         p.expect_exact(1)
     with self.assertRaisesRegexp(TypeError, ".*must be one of"):
         p.expect_exact([1, b"2"])
示例#2
0
 def test_bad_arg(self):
     p = PopenSpawn("cat")
     with self.assertRaisesRegexp(TypeError, ".*must be one of"):
         p.expect(1)
     with self.assertRaisesRegexp(TypeError, ".*must be one of"):
         p.expect([1, b"2"])
     with self.assertRaisesRegexp(TypeError, ".*must be one of"):
         p.expect_exact(1)
     with self.assertRaisesRegexp(TypeError, ".*must be one of"):
         p.expect_exact([1, b"2"])
示例#3
0
 def test_bad_arg(self):
     p = PopenSpawn('cat')
     with self.assertRaisesRegexp(TypeError, '.*must be one of'):
         p.expect(1)
     with self.assertRaisesRegexp(TypeError, '.*must be one of'):
         p.expect([1, b'2'])
     with self.assertRaisesRegexp(TypeError, '.*must be one of'):
         p.expect_exact(1)
     with self.assertRaisesRegexp(TypeError, '.*must be one of'):
         p.expect_exact([1, b'2'])
示例#4
0
 def test_bad_arg(self):
     p = PopenSpawn('cat')
     with self.assertRaisesRegexp(TypeError, '.*must be one of'):
         p.expect(1)
     with self.assertRaisesRegexp(TypeError, '.*must be one of'):
         p.expect([1, b'2'])
     with self.assertRaisesRegexp(TypeError, '.*must be one of'):
         p.expect_exact(1)
     with self.assertRaisesRegexp(TypeError, '.*must be one of'):
         p.expect_exact([1, b'2'])
示例#5
0
class Simulate:
    def __init__(self, times=5):
        # 执行进程
        self.process = PopenSpawn('cmd', timeout=10)
        # 日志
        self.logFile = open("logfile.txt", 'wb')
        self.process.logfile = self.logFile
        # 功能序列
        self.d = self.reverse_dict()
        self.seq = ('login', 'register')
        # 测试次数
        self.times = times
        # self.choice = Backend.CHOICE

    # 功能与对应输入字符的映射(反转后台类对象字典)
    @staticmethod
    def reverse_dict():
        d = Backend.func_dict
        return dict(zip(d.values(), d.keys()))

    def common(self, choice):
        # 等待特定字符出现
        self.process.expect_exact("请输入您想使用的功能")
        # 命令行输入
        self.process.send(f'{choice}\n')

        self.process.expect_exact("请输入用户名")
        # 使用 faker 制造测试数据
        fake = Faker('zh_CN')
        self.process.send(fake.phone_number() + '\n')

        self.process.expect_exact("请输入密码")
        self.process.send(fake.password(length=random.randint(6, 16)) + '\n')

    # 模拟登录
    def login(self):
        self.common(self.d['login'])

    def register(self):
        self.common(self.d['register'])

    def exit(self):
        self.process.expect_exact("请输入您想使用的功能")
        self.process.send('q\n')
        # 不忘文件关闭
        self.logFile.close()

    def run(self):
        # 执行 homework, 开始测试
        self.process.sendline('python mini_system.py')
        # 输入 1 使用登录功能;输入 2 使用注册功能;输入 q 退出程序
        # File "K:\Anaconda\lib\site-packages\pexpect\spawnbase.py", line 138, in _coerce_expect_string
        #     return s.encode('ascii')
        for i in range(5):
            # 执行注册或登录测试
            exec(f'self.{random.choice(self.seq)}()')
        self.exit()
示例#6
0
    def test_expect_exact(self):
        the_old_way = subprocess.Popen(args=["ls", "-l", "/bin"], stdout=subprocess.PIPE).communicate()[0].rstrip()
        p = PopenSpawn("ls -l /bin")
        the_new_way = b""
        while 1:
            i = p.expect_exact([b"\n", pexpect.EOF])
            the_new_way = the_new_way + p.before
            if i == 1:
                break
            the_new_way += b"\n"
        the_new_way = the_new_way.rstrip()

        assert the_old_way == the_new_way, len(the_old_way) - len(the_new_way)
        p = PopenSpawn("echo hello.?world")
        i = p.expect_exact(b".?")
        self.assertEqual(p.before, b"hello")
        self.assertEqual(p.after, b".?")
示例#7
0
    def test_expect_exact(self):
        the_old_way = subprocess.Popen(args=['ls', '-l', '/bin'],
                                       stdout=subprocess.PIPE).communicate()[0].rstrip()
        p = PopenSpawn('ls -l /bin')
        the_new_way = b''
        while 1:
            i = p.expect_exact([b'\n', pexpect.EOF])
            the_new_way = the_new_way + p.before
            if i == 1:
                break
            the_new_way += b'\n'
        the_new_way = the_new_way.rstrip()

        assert the_old_way == the_new_way, len(the_old_way) - len(the_new_way)
        p = PopenSpawn('echo hello.?world')
        i = p.expect_exact(b'.?')
        self.assertEqual(p.before, b'hello')
        self.assertEqual(p.after, b'.?')
示例#8
0
    def test_expect_exact(self):
        the_old_way = subprocess.Popen(
            args=['ls', '-l',
                  '/bin'], stdout=subprocess.PIPE).communicate()[0].rstrip()
        p = PopenSpawn('ls -l /bin')
        the_new_way = b''
        while 1:
            i = p.expect_exact([b'\n', pexpect.EOF])
            the_new_way = the_new_way + p.before
            if i == 1:
                break
            the_new_way += b'\n'
        the_new_way = the_new_way.rstrip()

        assert the_old_way == the_new_way, len(the_old_way) - len(the_new_way)
        p = PopenSpawn('echo hello.?world')
        i = p.expect_exact(b'.?')
        self.assertEqual(p.before, b'hello')
        self.assertEqual(p.after, b'.?')
示例#9
0
    def test_expect_exact(self):
        the_old_way = (subprocess.Popen(
            args=["ls", "-l",
                  "/bin"], stdout=subprocess.PIPE).communicate()[0].rstrip())
        p = PopenSpawn("ls -l /bin")
        the_new_way = b""
        while 1:
            i = p.expect_exact([b"\n", pexpect.EOF])
            the_new_way = the_new_way + p.before
            if i == 1:
                break
            the_new_way += b"\n"
        the_new_way = the_new_way.rstrip()

        assert the_old_way == the_new_way, len(the_old_way) - len(the_new_way)
        p = PopenSpawn("echo hello.?world")
        i = p.expect_exact(b".?")
        self.assertEqual(p.before, b"hello")
        self.assertEqual(p.after, b".?")
示例#10
0
 def test_expect_exact_basic(self):
     p = PopenSpawn("cat", timeout=5)
     p.sendline(b"Hello")
     p.sendline(b"there")
     p.sendline(b"Mr. Python")
     p.expect_exact(b"Hello")
     p.expect_exact(b"there")
     p.expect_exact(b"Mr. Python")
     p.sendeof()
     p.expect_exact(pexpect.EOF)
示例#11
0
 def test_expect_exact_basic(self):
     p = PopenSpawn("cat", timeout=5)
     p.sendline(b"Hello")
     p.sendline(b"there")
     p.sendline(b"Mr. Python")
     p.expect_exact(b"Hello")
     p.expect_exact(b"there")
     p.expect_exact(b"Mr. Python")
     p.sendeof()
     p.expect_exact(pexpect.EOF)
示例#12
0
 def test_expect_exact_basic(self):
     p = PopenSpawn('cat', timeout=5)
     p.sendline(b'Hello')
     p.sendline(b'there')
     p.sendline(b'Mr. Python')
     p.expect_exact(b'Hello')
     p.expect_exact(b'there')
     p.expect_exact(b'Mr. Python')
     p.sendeof()
     p.expect_exact(pexpect.EOF)
示例#13
0
 def test_expect_exact_basic(self):
     p = PopenSpawn('cat', timeout=5)
     p.sendline(b'Hello')
     p.sendline(b'there')
     p.sendline(b'Mr. Python')
     p.expect_exact(b'Hello')
     p.expect_exact(b'there')
     p.expect_exact(b'Mr. Python')
     p.sendeof()
     p.expect_exact(pexpect.EOF)
示例#14
0
def launch_wasabi(wallet,
                  launch_path='',
                  destination='',
                  keepalive=True,
                  pwd=''):
    """
    Launch Wasabi daemon.
    Return pexpect child if successful or return False if Wasabi didn't
    started.
    Wasabi daemon's documentation:
    https://docs.wasabiwallet.io/using-wasabi/Daemon.html#headless-wasabi-daemon
    """
    command = ''
    # If no launch_path provided, assume deb package is installed.
    if not launch_path:
        command += 'wassabee mix'

    # If launch_path is provided, if WalletWasabi.Gui in it,
    # assume source code.
    elif 'WalletWasabi.Gui' in launch_path:
        command += 'dotnet run --mix'

    # If launch_path is provided, if WalletWasabi.Gui not in it,
    # assume targz.
    else:
        command += './wassabee mix'
    command += ' --wallet:{}'.format(wallet)

    if destination:
        command += ' --destination:{}'.format(destination)
    if keepalive:
        command += ' --keepalive'
    print('Starting Wasabi')
    if launch_path:
        try:
            wasabi_proc = PopenSpawn(command, cwd=launch_path)
        except FileNotFoundError as e:
            raise FileNotFoundError(
                str(e) + ', check that your ' +
                '"launch_path" setting is correct')
    else:
        wasabi_proc = PopenSpawn(command)
    index = wasabi_proc.expect_exact(
        ['Password:'******'selected wallet does not exist', TIMEOUT, EOF],
        timeout=30)
    if index == 0:
        wasabi_proc.sendline(pwd)
    elif index == 1:
        raise MyExceptions.WalletMissing(name + '.json wallet does not exist')
    elif index == 2:
        raise MyExceptions.ProcessTimeout('Wasabi process TIMEOUT')
    elif index == 3:
        raise EOFError('Pexpect EOF')
    index = wasabi_proc.expect_exact(
        ['Correct password', 'Wrong password', TIMEOUT, EOF], timeout=30)
    if index == 0:
        print('Wasabi daemon started')
    elif index == 1:
        wasabi_proc.kill(SIGTERM)
        wasabi_proc.wait()
        raise MyExceptions.WrongPassword('Wrong password')
    elif index == 2:
        raise MyExceptions.ProcessTimeout('Wasabi process TIMEOUT')
    elif index == 3:
        raise EOFError('Pexpect EOF')
    index = wasabi_proc.expect_exact(['Starting Wallet', TIMEOUT, EOF],
                                     timeout=30)
    if index == 0:
        print('Wallet starting')
    elif index == 1:
        raise MyExceptions.ProcessTimeout('Wasabi process TIMEOUT')
    elif index == 2:
        raise EOFError('Pexpect EOF')
    if is_wasabi_running():
        print('Wasabi started')
        return wasabi_proc
    else:
        return False
示例#15
0
 def test_timeout_none(self):
     p = PopenSpawn('echo abcdef', timeout=None)
     p.expect('abc')
     p.expect_exact('def')
     p.expect(pexpect.EOF)
示例#16
0
 def test_timeout_none(self):
     p = PopenSpawn("echo abcdef", timeout=None)
     p.expect("abc")
     p.expect_exact("def")
     p.expect(pexpect.EOF)
示例#17
0
 def test_timeout_none(self):
     p = PopenSpawn("echo abcdef", timeout=None)
     p.expect("abc")
     p.expect_exact("def")
     p.expect(pexpect.EOF)
示例#18
0
 def test_timeout_none(self):
     p = PopenSpawn('echo abcdef', timeout=None)
     p.expect('abc')
     p.expect_exact('def')
     p.expect(pexpect.EOF)
示例#19
0
class SerAPI:
    def __init__(self, timeout, debug=False):
        'Initialize the SerAPI subprocess'
        self.debug = debug
        try:
            self.proc = PopenSpawn('sertop --implicit --omit_loc --print0',
                                   encoding='utf-8',
                                   timeout=timeout,
                                   maxread=10000000)
        except FileNotFoundError:
            log(
                'Please make sure the "sertop" program is in the PATH.\nYou may have to run "eval $(opam env)".',
                'ERROR')
            sys.exit(1)
        self.proc.expect_exact(
            '(Feedback((doc_id 0)(span_id 1)(route 0)(contents Processed)))\0')
        self.send('Noop')
        self.states_stack = []

        # global printing options
        self.execute('Unset Printing Notations.')
        self.execute('Unset Printing Wildcard.')
        self.execute('Set Printing Coercions.')
        self.execute('Unset Printing Allow Match Default Clause.')
        self.execute('Unset Printing Factorizable Match Patterns.')
        self.execute('Unset Printing Compact Contexts.')
        self.execute('Set Printing Implicit.')
        self.execute('Set Printing Depth 999999.')
        self.execute('Unset Printing Records.')

        # initialize the state stack
        self.push()

        self.ast_cache = {}
        self.dead = False

    def set_timeout(self, timeout):
        self.proc.timeout = timeout

    def get_timeout(self):
        return proc.timeout

    def send(self, cmd):
        'Send a command to SerAPI and retrieve the responses'
        #print(cmd)
        assert '\n' not in cmd
        self.proc.sendline(cmd)
        try:
            self.proc.expect([
                '\(Answer \d+ Ack\)\x00.*\(Answer \d+ Completed\)\x00',
                '\(Answer \d+ Ack\)\x00.*\(Answer \d+\(CoqExn.*\)\x00'
            ])
        except pexpect.TIMEOUT as ex:
            print(self.proc.before)
            raise CoqTimeout
        raw_responses = self.proc.after
        #print(raw_responses)
        ack_num = int(
            re.search(r'^\(Answer (?P<num>\d+)', raw_responses)['num'])
        for num in re.findall(r'(?<=\(Answer) \d+', raw_responses):
            assert int(num) == ack_num
        responses = []
        msg_str = []
        for item in raw_responses.split('\x00'):
            item = item.strip()
            if item == '':
                continue
            if not item.startswith('(Feedback') and not item.startswith(
                    '(Answer'):
                m = re.search(r'\(Feedback|\(Answer', item)
                if m is None:
                    continue
                item = item[m.span()[0]:]
                assert item.endswith(')')
            parsed_item = sexpdata.loads(item, nil=None, true=None)
            if 'CoqExn' in item:  # an error occured in Coq
                assert parsed_item[2][0] == Symbol('CoqExn')
                raise CoqExn(sexpdata.dumps(parsed_item[2][4]),
                             sexpdata.dumps(parsed_item[2]))
            if item.startswith('(Feedback'):  # ignore Feedback for now
                try:
                    msg = parsed_item[1][3][1]
                    if isinstance(msg,
                                  list) and msg != [] and msg[0] == Symbol(
                                      'Message'):
                        msg_sexp, _ = self.send(
                            '(Print ((pp_format PpStr)) (CoqPp %s))' %
                            sexpdata.dumps(msg[3]))
                        msg_str.extend(
                            [symbol2str(x[1]) for x in msg_sexp[1][2][1]])
                except IndexError:
                    pass
                continue
            responses.append(parsed_item)
        msg_str = '\n'.join(msg_str)
        return responses, raw_responses

    def send_add(self, cmd, return_ast):
        'Send a (Add () "XXX") command to SerAPI, return the state id and optionally the AST'
        responses, raw_responses = self.send('(Add () "%s")' % escape(cmd))
        state_ids = [
            int(sid) for sid in ADDED_STATE_PATTERN.findall(raw_responses)
        ]
        state_id = state_ids[-1]
        if self.states_stack != []:
            self.states_stack[-1].append(state_id)
        if return_ast:
            if cmd not in self.ast_cache:
                self.ast_cache[cmd] = self.query_ast(cmd)
            ast = self.ast_cache[cmd]
        else:
            ast = None
        return state_id, ast

    def query_ast(self, cmd):
        'Query the AST of the vernac command just added'
        responses, _ = self.send('(Parse () "%s")' % escape(cmd))
        ast = responses[1][2][1][0]
        assert ast[0] == Symbol('CoqAst')
        return ast

    def query_library(self, lib):
        responses, _ = self.send('(Query () (LocateLibrary "%s"))' % lib)
        physical_path = symbol2str(responses[1][2][1][0][3])
        return physical_path

    def query_qualid(self, qualid):
        responses, _ = self.send('(Query () (Locate "%s"))' % qualid)
        if responses[1][2][1] == [] and qualid.startswith('SerTop.'):
            qualid = qualid[len('SerTop.'):]
            responses, _ = self.send('(Query () (Locate "%s"))' % qualid)
        assert len(responses[1][2][1]) == 1
        short_responses = responses[1][2][1][0][1][0][1]
        assert short_responses[1][0] == Symbol('DirPath')
        short_ident = '.'.join(
            [symbol2str(x[1]) for x in short_responses[1][1][::-1]] +
            [symbol2str(short_responses[2][1])])
        return short_ident

    def query_env(self, current_file):
        'Query the global environment'
        responses, _ = self.send('(Query () Env)')
        env = responses[1][2][1][0]

        # store the constants
        constants = []
        for const in env[1][0][1][0][1]:
            # identifier
            qualid = print_mod_path(const[0][1]) + '.' + \
                '.'.join([symbol2str(x[1]) for x in const[0][2][1][::-1]] + [symbol2str(const[0][3][1])])
            if qualid.startswith('SerTop.'):
                logical_path = 'SerTop'
                physical_path = current_file
            else:
                logical_path = mod_path_file(const[0][1])
                assert qualid.startswith(logical_path)
                physical_path = os.path.relpath(
                    self.query_library(logical_path))
            physical_path += ':' + qualid[len(logical_path) + 1:]
            short_ident = self.query_qualid(qualid)
            # term
            assert const[1][0][1][0] == Symbol('const_body')
            if const[1][0][1][1][0] == Symbol('Undef'):  # delaration
                opaque = None
                term = None
            elif const[1][0][1][1][0] == Symbol(
                    'Def'):  # transparent definition
                opaque = False
                term = None
            else:
                assert const[1][0][1][1][0] == Symbol(
                    'OpaqueDef')  # opaque definition
                opaque = True
                term = None
            # type
            assert const[1][0][2][0] == Symbol('const_type')
            type_sexp = sexpdata.dumps(const[1][0][2][1])
            type = self.print_constr(type_sexp)
            sort = self.query_type(type_sexp, return_str=True)
            constants.append({
                'physical_path': physical_path,
                'short_ident': short_ident,
                'qualid': qualid,
                'term': term,
                'type': type,
                'sort': sort,
                'opaque': opaque,
                'sexp': sexpdata.dumps(const[1][0][2][1])
            })

        # store the inductives
        inductives = []
        for induct in env[1][0][1][1][1]:
            # identifier
            qualid = print_mod_path(induct[0][1]) + '.' + \
                '.'.join([symbol2str(x[1]) for x in induct[0][2][1][::-1]] + [symbol2str(induct[0][3][1])])
            short_ident = self.query_qualid(qualid)
            if qualid.startswith('SerTop.'):
                logical_path = 'SerTop'
                physical_path = current_file
            else:
                logical_path = mod_path_file(induct[0][1])
                physical_path = os.path.relpath(
                    self.query_library(logical_path))
            assert qualid.startswith(logical_path)
            physical_path += ':' + qualid[len(logical_path) + 1:]
            # blocks
            blocks = []
            for blk in induct[1][0][0][1]:
                blk_qualid = '.'.join(
                    qualid.split('.')[:-1] + [symbol2str(blk[0][1][1])])
                blk_short_ident = self.query_qualid(blk_qualid)
                # constructors
                constructors = []
                for c_name, c_type in zip(blk[3][1], blk[4][1]):
                    c_name = symbol2str(c_name[1])
                    c_type = self.print_constr(sexpdata.dumps(c_type))
                    #if c_type is not None:
                    #    c_type = UNBOUND_REL_PATTERN.sub(short_ident, c_type)
                    constructors.append((c_name, c_type))
                blocks.append({
                    'short_ident': blk_short_ident,
                    'qualid': blk_qualid,
                    'constructors': constructors
                })

            inductives.append({
                'physical_path':
                physical_path,
                'blocks':
                blocks,
                'is_record':
                induct[1][0][1][1] != Symbol('NotRecord'),
                'sexp':
                sexpdata.dumps(induct)
            })

        return constants, inductives

    def query_goals(self):
        'Retrieve a list of open goals'
        responses, _ = self.send('(Query () Goals)')
        assert responses[1][2][0] == Symbol('ObjList')
        if responses[1][2][1] == []:  #  no goals
            return [], [], [], []
        else:
            assert len(responses[1][2][1]) == 1

            def store_goals(goals_sexp):
                goals = []
                for g in goals_sexp:
                    hypotheses = []
                    for h in g[2][1]:
                        h_sexp = sexpdata.dumps(h[2])
                        hypotheses.append({
                            'idents':
                            [symbol2str(ident[1]) for ident in h[0][::-1]],
                            'term': [
                                None if t == [] else self.print_constr(
                                    sexpdata.dumps(t)) for t in h[1]
                            ],
                            'type':
                            self.print_constr(h_sexp),
                            'sexp':
                            h_sexp
                        })

                    type_sexp = sexpdata.dumps(g[1][1])
                    goals.append({
                        'id': int(g[0][1]),
                        'type': self.print_constr(type_sexp),
                        'sexp': type_sexp,
                        'hypotheses': hypotheses[::-1]
                    })
                return goals

            fg_goals = store_goals(responses[1][2][1][0][1][0][1])
            bg_goals = store_goals(
                list(
                    chain.from_iterable(
                        chain.from_iterable(responses[1][2][1][0][1][1][1]))))
            shelved_goals = store_goals(responses[1][2][1][0][1][2][1])
            given_up_goals = store_goals(responses[1][2][1][0][1][3][1])
            return fg_goals, bg_goals, shelved_goals, given_up_goals

    def has_open_goals(self):
        responses, _ = self.send('(Query () Goals)')
        assert responses[1][2][0] == Symbol('ObjList')
        return responses[1][2][1] != []

    def print_constr(self, sexp_str):
        if not hasattr(self, 'constr_cache'):
            self.constr_cache = {}
        if sexp_str not in self.constr_cache:
            try:
                responses, _ = self.send(
                    '(Print ((pp_format PpStr)) (CoqConstr %s))' % sexp_str)
                self.constr_cache[sexp_str] = normalize_spaces(
                    symbol2str(responses[1][2][1][0][1]))
            except CoqExn as ex:
                if ex.err_msg == 'Not_found':
                    return None
                else:
                    raise ex
            except TypeError as ex:
                self.constr_cache[sexp_str] = normalize_spaces(
                    symbol2str(responses[0][2][1][0][1]))
        return self.constr_cache[sexp_str]

    def query_vernac(self, cmd):
        return self.send('(Query () (Vernac "%s"))' % escape(cmd))

    def query_type(self, term_sexp, return_str=False):
        try:
            responses, _ = self.send('(Query () (Type %s))' % term_sexp)
        except CoqExn as ex:
            if ex.err_msg == 'Not_found':
                return None
            else:
                raise ex
        assert responses[1][2][1][0][0] == Symbol('CoqConstr')
        type_sexp = responses[1][2][1][0][1]
        if return_str:
            return self.print_constr(sexpdata.dumps(type_sexp))
        else:
            return type_sexp

    def execute(self, cmd, return_ast=False):
        'Execute a vernac command'
        state_id, ast = self.send_add(cmd, return_ast)
        responses, _ = self.send('(Exec %d)' % state_id)
        return responses, sexpdata.dumps(ast)

    def push(self):
        'push a new frame on the state stack (a checkpoint), which can be used to roll back to the current state'
        self.states_stack.append([])

    def cancel(self, states):
        self.send('(Cancel (%s))' % ' '.join([str(s) for s in states]))

    def pull(self):
        'remove a checkpoint created by push'
        states = self.states_stack.pop()
        self.states_stack[-1].extend(states)

    def pop(self):
        'rollback to a checkpoint created by push'
        self.cancel(self.states_stack.pop())

    def clean(self):
        self.proc.sendeof()
        self.proc.wait()
        self.dead = True

    def shutdown(self):
        self.proc.kill(signal.SIGKILL)
        self.dead = True

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.clean()
示例#20
0
class SerAPI:
    def __init__(self, timeout, debug=False):
        "Initialize the SerAPI subprocess"
        self.debug = debug
        try:
            self.proc = PopenSpawn(
                "sertop --implicit --omit_loc --print0",
                encoding="utf-8",
                timeout=timeout,
                maxread=10000000,
            )
        except FileNotFoundError:
            log(
                'Please make sure the "sertop" program is in the PATH.\nYou may have to run "eval $(opam env)".',
                "ERROR",
            )
            sys.exit(1)
        self.proc.expect_exact(
            "(Feedback((doc_id 0)(span_id 1)(route 0)(contents Processed)))\0")
        self.send("Noop")
        self.states_stack = []

        # global printing options
        self.execute("Unset Printing Notations.")
        self.execute("Unset Printing Wildcard.")
        self.execute("Set Printing Coercions.")
        self.execute("Unset Printing Allow Match Default Clause.")
        self.execute("Unset Printing Factorizable Match Patterns.")
        self.execute("Unset Printing Compact Contexts.")
        self.execute("Set Printing Implicit.")
        self.execute("Set Printing Depth 999999.")
        self.execute("Unset Printing Records.")

        # initialize the state stack
        self.push()

        self.ast_cache = {}
        self.dead = False

    def set_timeout(self, timeout):
        self.proc.timeout = timeout

    def get_timeout(self):
        return proc.timeout

    def send(self, cmd):
        "Send a command to SerAPI and retrieve the responses"
        # print(cmd)
        assert "\n" not in cmd
        self.proc.sendline(cmd)
        try:
            self.proc.expect([
                "\(Answer \d+ Ack\)\x00.*\(Answer \d+ Completed\)\x00",
                "\(Answer \d+ Ack\)\x00.*\(Answer \d+\(CoqExn.*\)\x00",
            ])
        except pexpect.TIMEOUT as ex:
            print(self.proc.before)
            raise CoqTimeout
        raw_responses = self.proc.after
        # print(raw_responses)
        ack_num = int(
            re.search(r"^\(Answer (?P<num>\d+)", raw_responses)["num"])
        for num in re.findall(r"(?<=\(Answer) \d+", raw_responses):
            assert int(num) == ack_num
        responses = []
        msg_str = []
        for item in raw_responses.split("\x00"):
            item = item.strip()
            if item == "":
                continue
            if not item.startswith("(Feedback") and not item.startswith(
                    "(Answer"):
                m = re.search(r"\(Feedback|\(Answer", item)
                if m is None:
                    continue
                item = item[m.span()[0]:]
                assert item.endswith(")")
            parsed_item = sexpdata.loads(item, nil=None, true=None)
            if "CoqExn" in item:  # an error occured in Coq
                assert parsed_item[2][0] == Symbol("CoqExn")
                raise CoqExn(sexpdata.dumps(parsed_item[2][4]),
                             sexpdata.dumps(parsed_item[2]))
            if item.startswith("(Feedback"):  # ignore Feedback for now
                try:
                    msg = parsed_item[1][3][1]
                    if (isinstance(msg, list) and msg != []
                            and msg[0] == Symbol("Message")):
                        msg_sexp, _ = self.send(
                            "(Print ((pp_format PpStr)) (CoqPp %s))" %
                            sexpdata.dumps(msg[3]))
                        msg_str.extend(
                            [symbol2str(x[1]) for x in msg_sexp[1][2][1]])
                except IndexError:
                    pass
                continue
            responses.append(parsed_item)
        msg_str = "\n".join(msg_str)
        return responses, raw_responses

    def send_add(self, cmd, return_ast):
        'Send a (Add () "XXX") command to SerAPI, return the state id and optionally the AST'
        responses, raw_responses = self.send('(Add () "%s")' % escape(cmd))
        state_ids = [
            int(sid) for sid in ADDED_STATE_PATTERN.findall(raw_responses)
        ]
        state_id = state_ids[-1]
        if self.states_stack != []:
            self.states_stack[-1].append(state_id)
        if return_ast:
            if cmd not in self.ast_cache:
                self.ast_cache[cmd] = self.query_ast(cmd)
            ast = self.ast_cache[cmd]
        else:
            ast = None
        return state_id, ast

    def query_ast(self, cmd):
        "Query the AST of the vernac command just added"
        responses, _ = self.send('(Parse () "%s")' % escape(cmd))
        ast = responses[1][2][1][0]
        assert ast[0] == Symbol("CoqAst")
        return ast

    def query_library(self, lib):
        responses, _ = self.send('(Query () (LocateLibrary "%s"))' % lib)
        physical_path = symbol2str(responses[1][2][1][0][3])
        return physical_path

    def query_qualid(self, qualid):
        responses, _ = self.send('(Query () (Locate "%s"))' % qualid)
        if responses[1][2][1] == [] and qualid.startswith("SerTop."):
            qualid = qualid[len("SerTop."):]
            responses, _ = self.send('(Query () (Locate "%s"))' % qualid)
        assert len(responses[1][2][1]) == 1
        short_responses = responses[1][2][1][0][1][0][1]
        assert short_responses[1][0] == Symbol("DirPath")
        short_ident = ".".join(
            [symbol2str(x[1]) for x in short_responses[1][1][::-1]] +
            [symbol2str(short_responses[2][1])])
        return short_ident

    def query_env(self, current_file):
        "Query the global environment"
        responses, _ = self.send("(Query () Env)")
        env = responses[1][2][1][0]

        # store the constants
        constants = []
        for const in env[1][0][1][0][1]:
            # identifier
            qualid = (
                print_mod_path(const[0][1]) + "." +
                ".".join([symbol2str(x[1]) for x in const[0][2][1][::-1]] +
                         [symbol2str(const[0][3][1])]))
            if qualid.startswith("SerTop."):
                logical_path = "SerTop"
                physical_path = current_file
            else:
                logical_path = mod_path_file(const[0][1])
                assert qualid.startswith(logical_path)
                physical_path = os.path.relpath(
                    self.query_library(logical_path))
            physical_path += ":" + qualid[len(logical_path) + 1:]
            short_ident = self.query_qualid(qualid)
            # term
            assert const[1][0][1][0] == Symbol("const_body")
            if const[1][0][1][1][0] == Symbol("Undef"):  # delaration
                opaque = None
                term = None
            elif const[1][0][1][1][0] == Symbol(
                    "Def"):  # transparent definition
                opaque = False
                term = None
            else:
                assert const[1][0][1][1][0] == Symbol(
                    "OpaqueDef")  # opaque definition
                opaque = True
                term = None
            # type
            assert const[1][0][2][0] == Symbol("const_type")
            type_sexp = sexpdata.dumps(const[1][0][2][1])
            type = self.print_constr(type_sexp)
            sort = self.query_type(type_sexp, return_str=True)
            constants.append({
                "physical_path": physical_path,
                "short_ident": short_ident,
                "qualid": qualid,
                "term": term,
                "type": type,
                "sort": sort,
                "opaque": opaque,
                "sexp": sexpdata.dumps(const[1][0][2][1]),
            })

        # store the inductives
        inductives = []
        for induct in env[1][0][1][1][1]:
            # identifier
            qualid = (
                print_mod_path(induct[0][1]) + "." +
                ".".join([symbol2str(x[1]) for x in induct[0][2][1][::-1]] +
                         [symbol2str(induct[0][3][1])]))
            short_ident = self.query_qualid(qualid)
            if qualid.startswith("SerTop."):
                logical_path = "SerTop"
                physical_path = current_file
            else:
                logical_path = mod_path_file(induct[0][1])
                physical_path = os.path.relpath(
                    self.query_library(logical_path))
            assert qualid.startswith(logical_path)
            physical_path += ":" + qualid[len(logical_path) + 1:]
            # blocks
            blocks = []
            for blk in induct[1][0][0][1]:
                blk_qualid = ".".join(
                    qualid.split(".")[:-1] + [symbol2str(blk[0][1][1])])
                blk_short_ident = self.query_qualid(blk_qualid)
                # constructors
                constructors = []
                for c_name, c_type in zip(blk[3][1], blk[4][1]):
                    c_name = symbol2str(c_name[1])
                    c_type = self.print_constr(sexpdata.dumps(c_type))
                    # if c_type is not None:
                    #    c_type = UNBOUND_REL_PATTERN.sub(short_ident, c_type)
                    constructors.append((c_name, c_type))
                blocks.append({
                    "short_ident": blk_short_ident,
                    "qualid": blk_qualid,
                    "constructors": constructors,
                })

            inductives.append({
                "physical_path":
                physical_path,
                "blocks":
                blocks,
                "is_record":
                induct[1][0][1][1] != Symbol("NotRecord"),
                "sexp":
                sexpdata.dumps(induct),
            })

        return constants, inductives

    def query_goals(self):
        "Retrieve a list of open goals"
        responses, _ = self.send("(Query () Goals)")
        assert responses[1][2][0] == Symbol("ObjList")
        if responses[1][2][1] == []:  #  no goals
            return [], [], [], []
        else:
            assert len(responses[1][2][1]) == 1

            def store_goals(goals_sexp):
                goals = []
                for g in goals_sexp:
                    hypotheses = []
                    for h in g[2][1]:
                        h_sexp = sexpdata.dumps(h[2])
                        hypotheses.append({
                            "idents":
                            [symbol2str(ident[1]) for ident in h[0][::-1]],
                            "term": [
                                None if t == [] else self.print_constr(
                                    sexpdata.dumps(t)) for t in h[1]
                            ],
                            "type":
                            self.print_constr(h_sexp),
                            "sexp":
                            h_sexp,
                        })

                    type_sexp = sexpdata.dumps(g[1][1])
                    goals.append({
                        "id": int(g[0][1]),
                        "type": self.print_constr(type_sexp),
                        "sexp": type_sexp,
                        "hypotheses": hypotheses[::-1],
                    })
                return goals

            fg_goals = store_goals(responses[1][2][1][0][1][0][1])
            bg_goals = store_goals(
                list(
                    chain.from_iterable(
                        chain.from_iterable(responses[1][2][1][0][1][1][1]))))
            shelved_goals = store_goals(responses[1][2][1][0][1][2][1])
            given_up_goals = store_goals(responses[1][2][1][0][1][3][1])
            return fg_goals, bg_goals, shelved_goals, given_up_goals

    def has_open_goals(self):
        responses, _ = self.send("(Query () Goals)")
        assert responses[1][2][0] == Symbol("ObjList")
        return responses[1][2][1] != []

    def print_constr(self, sexp_str):
        if not hasattr(self, "constr_cache"):
            self.constr_cache = {}
        if sexp_str not in self.constr_cache:
            try:
                responses, _ = self.send(
                    "(Print ((pp_format PpStr)) (CoqConstr %s))" % sexp_str)
                self.constr_cache[sexp_str] = normalize_spaces(
                    symbol2str(responses[1][2][1][0][1]))
            except CoqExn as ex:
                if ex.err_msg == "Not_found":
                    return None
                else:
                    raise ex
            except TypeError as ex:
                self.constr_cache[sexp_str] = normalize_spaces(
                    symbol2str(responses[0][2][1][0][1]))
        return self.constr_cache[sexp_str]

    def query_vernac(self, cmd):
        return self.send('(Query () (Vernac "%s"))' % escape(cmd))

    def query_type(self, term_sexp, return_str=False):
        try:
            responses, _ = self.send("(Query () (Type %s))" % term_sexp)
        except CoqExn as ex:
            if ex.err_msg == "Not_found":
                return None
            else:
                raise ex
        assert responses[1][2][1][0][0] == Symbol("CoqConstr")
        type_sexp = responses[1][2][1][0][1]
        if return_str:
            return self.print_constr(sexpdata.dumps(type_sexp))
        else:
            return type_sexp

    def execute(self, cmd, return_ast=False):
        "Execute a vernac command"
        state_id, ast = self.send_add(cmd, return_ast)
        responses, _ = self.send("(Exec %d)" % state_id)
        return responses, sexpdata.dumps(ast)

    def push(self):
        "push a new frame on the state stack (a checkpoint), which can be used to roll back to the current state"
        self.states_stack.append([])

    def cancel(self, states):
        self.send("(Cancel (%s))" % " ".join([str(s) for s in states]))

    def pull(self):
        "remove a checkpoint created by push"
        states = self.states_stack.pop()
        self.states_stack[-1].extend(states)
        return len(states)

    def pop(self):
        "rollback to a checkpoint created by push"
        self.cancel(self.states_stack.pop())

    def pop_n(self, cnt):
        states = []
        for i in range(cnt):
            states.append(self.states_stack[-1].pop())
        self.cancel(states)

    def clean(self):
        self.proc.sendeof()
        self.proc.wait()
        self.dead = True

    def shutdown(self):
        self.proc.kill(signal.SIGKILL)
        self.dead = True

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.clean()