1local compl = require("awful.completion")
2local shell = function(...)
3    local command, pos, matches = compl.shell(...)
4    return {command, pos, matches}
5end
6local gfs = require("gears.filesystem")
7local Gio = require("lgi").Gio
8local GLib = require("lgi").GLib
9local lfs = require("lfs")
10
11local has_bash = GLib.find_program_in_path("bash")
12local has_zsh = GLib.find_program_in_path("zsh")
13
14if not has_bash and not has_zsh then
15    print('Skipping spec/awful/completion_spec.lua: bash and zsh are not installed.')
16    return
17end
18
19local orig_path_env = GLib.getenv("PATH")
20local required_commands = {"bash", "zsh", "sort"}
21
22-- Change PATH
23local function get_test_path_dir()
24    local test_path = Gio.File.new_tmp("awesome-tests-path-XXXXXX")
25    test_path:delete()
26    test_path:make_directory()
27
28    for _, cmd in ipairs(required_commands) do
29        local target = GLib.find_program_in_path(cmd)
30        if target then
31            test_path:get_child(cmd):make_symbolic_link(target)
32        end
33    end
34
35    test_path = test_path:get_path()
36    GLib.setenv("PATH", test_path, true)
37    return test_path
38end
39
40-- Reset PATH
41local function remove_test_path_dir(test_path)
42    GLib.setenv("PATH", orig_path_env, true)
43    for _, cmd in ipairs(required_commands) do
44        os.remove(test_path .. "/" .. cmd)
45    end
46    os.remove(test_path)
47end
48
49local test_dir
50local test_path
51
52--- Get and create a temporary test dir based on `pat`, where %d gets replaced by
53-- the current PID.
54local function get_test_dir()
55    local tfile = Gio.File.new_tmp("awesome-tests-XXXXXX")
56    local path = tfile:get_path()
57    tfile:delete()
58    gfs.make_directories(path)
59    return path
60end
61
62describe("awful.completion.shell in empty directory", function()
63    local orig_dir = lfs.currentdir()
64
65    setup(function()
66        test_dir = get_test_dir()
67        lfs.chdir(test_dir)
68        test_path = get_test_path_dir()
69    end)
70
71    teardown(function()
72        assert.True(os.remove(test_dir))
73        remove_test_path_dir(test_path)
74        lfs.chdir(orig_dir)
75    end)
76
77    if has_bash then
78        it("handles completion of true (bash)", function()
79            assert.same(shell('true', 5, 1, 'bash'), {'true', 5, {'true'}})
80        end)
81    end
82    if has_zsh then
83        it("handles completion of true (zsh)", function()
84            assert.same(shell('true', 5, 1, 'zsh'), {'true', 5, {'true'}})
85        end)
86    end
87    it("handles completion of true (nil)", function()
88        assert.same(shell('true', 5, 1, nil), {'true', 5, {'true'}})
89    end)
90end)
91
92describe("awful.completion.shell", function()
93    local orig_dir = lfs.currentdir()
94
95    setup(function()
96        test_dir = get_test_dir()
97        gfs.make_directories(test_dir .. '/true')
98        Gio.File.new_for_path(test_dir .. '/true/with_file'):create(Gio.FileCreateFlags.NONE);
99        gfs.make_directories(test_dir .. '/just_a_directory')
100        Gio.File.new_for_path(test_dir .. '/just_a_directory/with_file'):create(Gio.FileCreateFlags.NONE);
101        -- Chaotic order is intended!
102        gfs.make_directories(test_dir .. '/ambiguous_dir_a')
103        gfs.make_directories(test_dir .. '/ambiguous_dir_e')
104        Gio.File.new_for_path(test_dir .. '/ambiguous_dir_e/with_file'):create(Gio.FileCreateFlags.NONE);
105        gfs.make_directories(test_dir .. '/ambiguous_dir_c')
106        Gio.File.new_for_path(test_dir .. '/ambiguous_dir_c/with_file'):create(Gio.FileCreateFlags.NONE);
107        gfs.make_directories(test_dir .. '/ambiguous_dir_d')
108        gfs.make_directories(test_dir .. '/ambiguous_dir_b')
109        gfs.make_directories(test_dir .. '/ambiguous_dir_f')
110        os.execute(string.format(
111            'cd %s && touch localcommand && chmod +x localcommand', test_dir))
112        lfs.chdir(test_dir)
113        test_path = get_test_path_dir()
114    end)
115
116    teardown(function()
117        assert.True(os.remove(test_dir .. '/ambiguous_dir_a'))
118        assert.True(os.remove(test_dir .. '/ambiguous_dir_b'))
119        assert.True(os.remove(test_dir .. '/ambiguous_dir_c/with_file'))
120        assert.True(os.remove(test_dir .. '/ambiguous_dir_c'))
121        assert.True(os.remove(test_dir .. '/ambiguous_dir_d'))
122        assert.True(os.remove(test_dir .. '/ambiguous_dir_e/with_file'))
123        assert.True(os.remove(test_dir .. '/ambiguous_dir_e'))
124        assert.True(os.remove(test_dir .. '/ambiguous_dir_f'))
125        assert.True(os.remove(test_dir .. '/just_a_directory/with_file'))
126        assert.True(os.remove(test_dir .. '/just_a_directory'))
127        assert.True(os.remove(test_dir .. '/localcommand'))
128        assert.True(os.remove(test_dir .. '/true/with_file'))
129        assert.True(os.remove(test_dir .. '/true'))
130        assert.True(os.remove(test_dir))
131        remove_test_path_dir(test_path)
132        lfs.chdir(orig_dir)
133    end)
134
135    if has_bash then
136        it("handles empty input (bash)", function()
137            assert.same(shell('', 1, 1, 'bash'), {'', 1})
138        end)
139    end
140    if has_zsh then
141        it("handles empty input (zsh)", function()
142            assert.same(shell('', 1, 1, 'zsh'), {'', 1})
143        end)
144    end
145    it("handles empty input (nil)", function()
146        assert.same(shell('', 1, 1, nil), {'', 1})
147    end)
148
149    if has_bash then
150        it("completes local command (bash)", function()
151            assert.same(shell('./localcomm', 12, 1, 'bash'), {'./localcommand', 15, {'./localcommand'}})
152        end)
153    end
154    if has_zsh then
155        it("completes local command (zsh)", function()
156            assert.same(shell('./localcomm', 12, 1, 'zsh'), {'./localcommand', 15, {'./localcommand'}})
157        end)
158    end
159
160    if has_bash then
161        it("completes local file (bash)", function()
162            assert.same(shell('ls l', 5, 1, 'bash'), {'ls localcommand', 16, {'localcommand'}})
163        end)
164    end
165    if has_zsh then
166        it("completes local file (zsh)", function()
167            assert.same(shell('ls l', 5, 1, 'zsh'), {'ls localcommand', 16, {'localcommand'}})
168        end)
169    end
170
171    if has_bash then
172        it("completes command regardless of local directory (bash)", function()
173            assert.same(shell('true', 5, 1, 'bash'), {'true', 5, {'true'}})
174        end)
175    end
176    if has_zsh then
177        it("completes command regardless of local directory (zsh)", function()
178            assert.same(shell('true', 5, 1, 'zsh'), {'true', 5, {'true'}})
179        end)
180    end
181    it("completes command regardless of local directory (nil)", function()
182        assert.same(shell('true', 5, 1, nil), {'true', 5, {'true'}})
183    end)
184
185    if has_bash then
186        it("does not complete local directory not starting with ./ (bash)", function()
187            assert.same(shell('just_a', 7, 1, 'bash'), {'just_a', 7})
188        end)
189    end
190    if has_zsh then
191        it("does not complete local directory not starting with ./ (zsh)", function()
192            assert.same(shell('just_a', 7, 1, 'zsh'), {'just_a', 7})
193        end)
194    end
195
196    if has_bash then
197        it("completes local directories starting with ./ (bash)", function()
198            assert.same(shell('./just', 7, 1, 'bash'), {'./just_a_directory/', 20, {'./just_a_directory/'}})
199            assert.same(shell('./t', 4, 1, 'bash'), {'./true/', 8, {'./true/'}})
200        end)
201    end
202    if has_zsh then
203        it("completes local directories starting with ./ (zsh, non-empty)", function()
204            assert.same(shell('./just', 7, 1, 'zsh'), {'./just_a_directory/', 20, {'./just_a_directory/'}})
205            assert.same(shell('./t', 4, 1, 'zsh'), {'./true/', 8, {'./true/'}})
206        end)
207    end
208    --]]
209
210    if has_bash then
211        it("correctly sorts completed items (bash)", function()
212            assert.same(shell('./ambi', 7, 1, 'bash'), {'./ambiguous_dir_a/', 19,
213            {'./ambiguous_dir_a/', './ambiguous_dir_b/', './ambiguous_dir_c/',
214            './ambiguous_dir_d/', './ambiguous_dir_e/', './ambiguous_dir_f/'}})
215        end)
216    end
217    if has_zsh then
218        it("correctly sorts completed items (zsh)", function()
219            assert.same(shell('./ambi', 7, 1, 'zsh'), {'./ambiguous_dir_c/', 19,
220            {'./ambiguous_dir_c/', './ambiguous_dir_e/'}})
221        end)
222    end
223end)
224
225describe("awful.completion.shell handles $SHELL", function()
226    local orig_getenv = os.getenv
227    local gdebug = require("gears.debug")
228    local orig_print_warning
229    local print_warning_message
230    local os_getenv_shell
231
232    setup(function()
233        os.getenv = function(name)  -- luacheck: ignore
234            if name == 'SHELL' then return os_getenv_shell end
235            return orig_getenv(name)
236        end
237
238        orig_print_warning = gdebug.print_warning
239        gdebug.print_warning = function(message)
240            print_warning_message = message
241        end
242
243        test_path = get_test_path_dir()
244    end)
245
246    teardown(function()
247        os.getenv = orig_getenv  --luacheck: ignore
248        gdebug.print_warning = orig_print_warning
249        remove_test_path_dir(test_path)
250    end)
251
252    before_each(function()
253        compl.default_shell = nil
254        print_warning_message = nil
255    end)
256
257    it("falls back to bash if unset", function()
258        assert.same(shell('true', 5, 1, nil), {'true', 5, {'true'}})
259        assert.same(print_warning_message,
260                    'SHELL not set in environment, falling back to bash.')
261        assert.same(compl.default_shell, "bash")
262    end)
263
264    it("uses zsh from path", function()
265        os_getenv_shell = '/opt/bin/zsh'
266        assert.same(shell('true', 5, 1, nil), {'true', 5, {'true'}})
267        assert.same(compl.default_shell, "zsh")
268        assert.is_nil(print_warning_message)
269    end)
270
271    it("uses bash for unknown", function()
272        os_getenv_shell = '/dev/null'
273        assert.same(shell('true', 5, 1, nil), {'true', 5, {'true'}})
274        assert.same(compl.default_shell, "bash")
275        assert.is_nil(print_warning_message)
276    end)
277end)
278
279-- vim: ft=lua:et:sw=4:ts=8:sts=4:tw=80
280