summaryrefslogtreecommitdiff
path: root/test/agent_session_test.py
blob: ba20b5a9240345394a96f33dc35ce5a2a0e69118 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#!/usr/bin/env python3
import contextlib
import argparse
import errno
import io
import json
from pathlib import Path
import subprocess
import tempfile
import time

import agent_session


def check(binary, options, command, expected, phase=None, cleanup_failure=False):
    children = []
    original_popen = subprocess.Popen

    def launch(*args, **kwargs):
        child = original_popen(*args, **kwargs)
        children.append(child)
        return child

    with tempfile.TemporaryDirectory(prefix='pardes-agent-driver-test-') as directory:
        original_cleanup = tempfile.TemporaryDirectory.cleanup

        def fail_cleanup(temporary):
            original_cleanup(temporary)
            raise OSError(errno.ENOTEMPTY, 'injected cleanup failure')

        previous_tempdir = tempfile.tempdir
        tempfile.tempdir = directory
        subprocess.Popen = launch
        if cleanup_failure:
            tempfile.TemporaryDirectory.cleanup = fail_cleanup
        stdout, stderr = io.StringIO(), io.StringIO()
        started = time.monotonic()
        try:
            with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr):
                try:
                    result = agent_session.main([binary, *options, '--', *command])
                except SystemExit as error:
                    result = error.code
        finally:
            subprocess.Popen = original_popen
            tempfile.tempdir = previous_tempdir
            tempfile.TemporaryDirectory.cleanup = original_cleanup
        assert result == expected, (result, expected, stderr.getvalue())
        assert all(child.poll() is not None for child in children), 'owned Pardes still running'
        assert not list(Path(directory).iterdir()), 'owned session files remain'
        if expected == 2:
            assert not children, 'invalid arguments started Pardes'
        elif expected == 0:
            metrics = json.loads(stdout.getvalue())
            assert metrics['body_bytes'] > 0 and metrics['body_rows'] > 0
            assert len(metrics['visible_text_sha256']) == len(metrics['body_sha256']) == 64
            assert metrics['ninep_write_to_observation_ms'] >= 0
            assert metrics['ninep_clear_to_observation_ms'] >= 0
            assert 'AGENT_READY' not in stdout.getvalue()
        else:
            assert not stdout.getvalue()
            error = json.loads(stderr.getvalue())
            assert error['phase'] == phase
            if cleanup_failure:
                assert error['error'] == 'OSError' and error['errno'] == errno.ENOTEMPTY
                assert error['metrics']['body_rows'] > 0
                assert error['expected_visible_text_sha256'] == error['observed_visible_text_sha256']
            else:
                assert error['error'] == 'TimeoutError'
            assert 'AGENT_READY' not in stderr.getvalue()
            assert time.monotonic() - started < 8, 'timeout did not bound cleanup'


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('binary', type=Path)
    parser.add_argument('--gui-grid', action='store_true')
    args = parser.parse_args()
    binary = str(args.binary.resolve())
    ready = ['--ready', 'AGENT_READY', *(['--gui-grid'] if args.gui_grid else [])]
    command = ['/bin/sh', '-c', 'printf "AGENT_READY> "; read answer']
    for options, argv in [
        ([], command),
        (ready + ['--timeout', 'nan'], command),
        (ready + ['--timeout', '0'], command),
        (ready + ['--timeout', '601'], command),
        (ready + ['--min-rows', '-1'], command),
        (ready + ['--prepare-key', '0a'], command),
        (ready + ['--prepare-key', '0d'], command),
        (ready + ['--prepare-key', '41'], command),
        (ready + ['--prepare-key', '0f0f'], command),
        (ready, []),
    ]:
        check(binary, options, argv, 2)
    check(binary, ready + ['--timeout', '10'], command, 0)
    check(binary, ready + ['--timeout', '10', '--min-rows', '1000'],
          ['/bin/sh', '-c', 'printf "AGENT_READY> "; sleep .3; seq 1 3000; printf "AGENT_READY> "; read answer'], 0)
    check(binary, ready + ['--timeout', '10', '--min-rows', '1000', '--prepare-key', '0f'],
          ['/bin/sh', '-c', 'stty raw -echo; printf "AGENT_READY> "; dd bs=1 count=1 of=/dev/null 2>/dev/null; '
           'stty sane; seq 1 3000; printf "AGENT_READY> "; read answer'], 0)
    check(binary, ready + ['--timeout', '2', '--min-rows', '1000'], command, 1, 'history')
    check(binary, ready + ['--timeout', '1'],
          ['/bin/sh', '-c', ': AGENT_READY; sleep 30'], 1, 'readiness')
    check(binary, ready + ['--timeout', '10'], command, 1, 'files-cleanup', cleanup_failure=True)
    print('agent-session: 16 functional argument, interaction, timeout and cleanup checks passed')


if __name__ == '__main__':
    main()