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 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
|
import asyncio
import os
from collections import OrderedDict
from pathlib import Path
from tempfile import TemporaryDirectory
from unittest.mock import Mock, patch
import pytest
from libqtile import utils
def test_rgb_from_hex_number():
assert utils.rgb("ff00ff") == (1, 0, 1, 1)
def test_rgb_from_hex_string():
assert utils.rgb("#00ff00") == (0, 1, 0, 1)
def test_rgb_from_hex_number_with_alpha():
assert utils.rgb("ff0000.3") == (1, 0, 0, 0.3)
def test_rgb_from_hex_string_with_alpha():
assert utils.rgb("#ff0000.5") == (1, 0, 0, 0.5)
def test_rgb_from_hex_number_with_hex_alpha():
assert utils.rgb("ff000000") == (1, 0, 0, 0.0)
def test_rgb_from_hex_string_with_hex_alpha():
assert utils.rgb("#ff000000") == (1, 0, 0, 0.0)
def test_rgb_from_base10_tuple():
assert utils.rgb([255, 255, 0]) == (1, 1, 0, 1)
def test_rgb_from_base10_tuple_with_alpha():
assert utils.rgb([255, 255, 0, 0.5]) == (1, 1, 0, 0.5)
def test_rgb_from_3_digit_hex_number():
assert utils.rgb("f0f") == (1, 0, 1, 1)
def test_rgb_from_3_digit_hex_string():
assert utils.rgb("#f0f") == (1, 0, 1, 1)
def test_rgb_from_3_digit_hex_number_with_alpha():
assert utils.rgb("f0f.5") == (1, 0, 1, 0.5)
def test_rgb_from_3_digit_hex_string_with_alpha():
assert utils.rgb("#f0f.5") == (1, 0, 1, 0.5)
def test_has_transparency():
colours = [
("#00000000", True),
("#000000ff", False),
("#ff00ff.5", True),
((255, 255, 255, 0.5), True),
((255, 255, 255), False),
(["#000000", "#ffffff"], False),
(["#000000", "#ffffffaa"], True),
]
for colour, expected in colours:
assert utils.has_transparency(colour) == expected
def test_remove_transparency():
colours = [
("#00000000", (0.0, 0.0, 0.0)),
("#ffffffff", (255.0, 255.0, 255.0)),
((255, 255, 255, 0.5), (255.0, 255.0, 255.0)),
((255, 255, 255), (255.0, 255.0, 255.0)),
(["#000000", "#ffffff"], [(0.0, 0.0, 0.0), (255.0, 255.0, 255.0)]),
(["#000000", "#ffffffaa"], [(0.0, 0.0, 0.0), (255.0, 255.0, 255.0)]),
]
for colour, expected in colours:
assert utils.remove_transparency(colour) == expected
def test_scrub_to_utf8():
assert utils.scrub_to_utf8(b"foo") == "foo"
def test_guess_terminal_accepts_a_preference(path):
term = "shitty"
Path(path, term).touch(mode=0o777)
assert utils.guess_terminal(term) == term
def test_guess_terminal_accepts_a_list_of_preferences(path):
term = "shitty"
Path(path, term).touch(mode=0o777)
assert utils.guess_terminal(["nutty", term]) == term
def test_guess_terminal_falls_back_to_defaults(path):
Path(path, "kitty").touch(mode=0o777)
assert utils.guess_terminal(["nutty", "witty", "petty"]) == "kitty"
@pytest.fixture
def path(monkeypatch):
"Create a TemporaryDirectory as the PATH"
with TemporaryDirectory() as d:
monkeypatch.setenv("PATH", d)
yield d
TEST_DIR = os.path.dirname(os.path.abspath(__file__))
DATA_DIR = os.path.join(TEST_DIR, "data")
class TestScanFiles:
def test_audio_volume_muted(self):
name = "audio-volume-muted.*"
dfiles = utils.scan_files(DATA_DIR, name)
result = dfiles[name]
assert len(result) == 2
png = os.path.join(DATA_DIR, "png", "audio-volume-muted.png")
assert png in result
svg = os.path.join(DATA_DIR, "svg", "audio-volume-muted.svg")
assert svg in result
def test_only_svg(self):
name = "audio-volume-muted.svg"
dfiles = utils.scan_files(DATA_DIR, name)
result = dfiles[name]
assert len(result) == 1
svg = os.path.join(DATA_DIR, "svg", "audio-volume-muted.svg")
assert svg in result
def test_multiple(self):
names = OrderedDict()
names["audio-volume-muted.*"] = 2
names["battery-caution-charging.*"] = 1
dfiles = utils.scan_files(DATA_DIR, *names)
for name, length in names.items():
assert len(dfiles[name]) == length
@pytest.mark.asyncio
async def test_acall_process_pid_tracking():
"""Test that acall_process tracks PIDs in ASYNC_PIDS and reap_zombies breaks when it finds tracked processes."""
waitid_calls = []
def mock_waitid(idtype, id_or_pid, options):
waitid_calls.append((idtype, id_or_pid, options))
# For the first call (P_ALL check), return a mock result with a PID that's being tracked
if idtype == os.P_ALL:
if len(waitid_calls) == 1:
# Return a tracked PID on the first call
mock_result = Mock()
mock_result.si_pid = 12345 # Mock PID that we'll add to ASYNC_PIDS
return mock_result
else:
# No more processes to reap
return None
# For P_PID calls (specific PID), just return None (handled)
return None
with patch("os.waitid", side_effect=mock_waitid):
utils.ASYNC_PIDS.add(12345)
utils.reap_zombies()
# Should only make one call (P_ALL) because it breaks when finding tracked PID
assert len(waitid_calls) == 1
# First call should be P_ALL to check for any zombies
first_call = waitid_calls[0]
assert first_call[0] == os.P_ALL
assert first_call[2] & os.WEXITED
assert first_call[2] & os.WNOHANG
assert first_call[2] & os.WNOWAIT
utils.ASYNC_PIDS.clear()
@pytest.mark.asyncio
async def test_acall_process_adds_removes_pid():
"""Test that acall_process properly adds and removes PIDs from ASYNC_PIDS."""
task = asyncio.create_task(utils.acall_process(["echo", "test"]))
result = await task
assert result.strip() == "test"
assert len(utils.ASYNC_PIDS) == 0
@pytest.mark.asyncio
async def test_concurrent_acall_processes():
"""Test that multiple concurrent acall_process calls track PIDs correctly."""
tasks = [asyncio.create_task(utils.acall_process(["echo", f"test{i}"])) for i in range(3)]
results = await asyncio.gather(*tasks)
for i, result in enumerate(results):
assert result.strip() == f"test{i}"
assert len(utils.ASYNC_PIDS) == 0
|