1from __future__ import absolute_import, division, print_function
2import subprocess
3import sys
4import pytest
5
6# test for _argcomplete but not specific for any application
7
8
9def equal_with_bash(prefix, ffc, fc, out=None):
10    res = ffc(prefix)
11    res_bash = set(fc(prefix))
12    retval = set(res) == res_bash
13    if out:
14        out.write("equal_with_bash %s %s\n" % (retval, res))
15        if not retval:
16            out.write(" python - bash: %s\n" % (set(res) - res_bash))
17            out.write(" bash - python: %s\n" % (res_bash - set(res)))
18    return retval
19
20
21# copied from argcomplete.completers as import from there
22# also pulls in argcomplete.__init__ which opens filedescriptor 9
23# this gives an IOError at the end of testrun
24
25
26def _wrapcall(*args, **kargs):
27    try:
28        if sys.version_info > (2, 7):
29            return subprocess.check_output(*args, **kargs).decode().splitlines()
30        if "stdout" in kargs:
31            raise ValueError("stdout argument not allowed, it will be overridden.")
32        process = subprocess.Popen(stdout=subprocess.PIPE, *args, **kargs)
33        output, unused_err = process.communicate()
34        retcode = process.poll()
35        if retcode:
36            cmd = kargs.get("args")
37            if cmd is None:
38                cmd = args[0]
39            raise subprocess.CalledProcessError(retcode, cmd)
40        return output.decode().splitlines()
41    except subprocess.CalledProcessError:
42        return []
43
44
45class FilesCompleter(object):
46    "File completer class, optionally takes a list of allowed extensions"
47
48    def __init__(self, allowednames=(), directories=True):
49        # Fix if someone passes in a string instead of a list
50        if type(allowednames) is str:
51            allowednames = [allowednames]
52
53        self.allowednames = [x.lstrip("*").lstrip(".") for x in allowednames]
54        self.directories = directories
55
56    def __call__(self, prefix, **kwargs):
57        completion = []
58        if self.allowednames:
59            if self.directories:
60                files = _wrapcall(
61                    ["bash", "-c", "compgen -A directory -- '{p}'".format(p=prefix)]
62                )
63                completion += [f + "/" for f in files]
64            for x in self.allowednames:
65                completion += _wrapcall(
66                    [
67                        "bash",
68                        "-c",
69                        "compgen -A file -X '!*.{0}' -- '{p}'".format(x, p=prefix),
70                    ]
71                )
72        else:
73            completion += _wrapcall(
74                ["bash", "-c", "compgen -A file -- '{p}'".format(p=prefix)]
75            )
76
77            anticomp = _wrapcall(
78                ["bash", "-c", "compgen -A directory -- '{p}'".format(p=prefix)]
79            )
80
81            completion = list(set(completion) - set(anticomp))
82
83            if self.directories:
84                completion += [f + "/" for f in anticomp]
85        return completion
86
87
88class TestArgComplete(object):
89
90    @pytest.mark.skipif("sys.platform in ('win32', 'darwin')")
91    def test_compare_with_compgen(self):
92        from _pytest._argcomplete import FastFilesCompleter
93
94        ffc = FastFilesCompleter()
95        fc = FilesCompleter()
96        for x in ["/", "/d", "/data", "qqq", ""]:
97            assert equal_with_bash(x, ffc, fc, out=sys.stdout)
98
99    @pytest.mark.skipif("sys.platform in ('win32', 'darwin')")
100    def test_remove_dir_prefix(self):
101        """this is not compatible with compgen but it is with bash itself:
102        ls /usr/<TAB>
103        """
104        from _pytest._argcomplete import FastFilesCompleter
105
106        ffc = FastFilesCompleter()
107        fc = FilesCompleter()
108        for x in "/usr/".split():
109            assert not equal_with_bash(x, ffc, fc, out=sys.stdout)
110