2024-02-12 09:03:25 -08:00
|
|
|
import pprint
|
2024-03-23 10:57:46 -07:00
|
|
|
import json
|
2022-02-02 18:25:55 -08:00
|
|
|
import re
|
|
|
|
import unittest
|
|
|
|
from typing import Optional
|
|
|
|
|
|
|
|
from jc.parsers.xrandr import (
|
2024-02-12 09:03:25 -08:00
|
|
|
Device,
|
|
|
|
Edid,
|
2024-02-12 15:05:44 -08:00
|
|
|
_Line,
|
2024-02-12 09:03:25 -08:00
|
|
|
LineType,
|
|
|
|
ResolutionMode,
|
|
|
|
Response,
|
|
|
|
Screen,
|
2022-02-02 18:25:55 -08:00
|
|
|
_device_pattern,
|
|
|
|
_frequencies_pattern,
|
2024-02-12 09:03:25 -08:00
|
|
|
_parse_device,
|
|
|
|
_parse_resolution_mode,
|
|
|
|
_parse_screen,
|
|
|
|
_resolution_mode_pattern,
|
|
|
|
_screen_pattern,
|
2022-02-02 18:25:55 -08:00
|
|
|
parse,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class XrandrTests(unittest.TestCase):
|
2022-02-14 09:14:38 -08:00
|
|
|
def test_xrandr_nodata(self):
|
|
|
|
"""
|
|
|
|
Test 'xrandr' with no data
|
|
|
|
"""
|
2024-02-12 09:03:25 -08:00
|
|
|
self.assertEqual(parse("", quiet=True), {"screens": []})
|
2022-02-14 09:14:38 -08:00
|
|
|
|
2022-02-02 18:25:55 -08:00
|
|
|
def test_regexes(self):
|
|
|
|
devices = [
|
|
|
|
"HDMI1 connected (normal left inverted right x axis y axis)",
|
|
|
|
"VIRTUAL1 disconnected (normal left inverted right x axis y axis)",
|
|
|
|
"eDP1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis) 310mm x 170mm",
|
2022-04-10 18:12:28 +09:00
|
|
|
"eDP-1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis) 309mm x 174mm",
|
|
|
|
"HDMI-0 connected 2160x3840+3840+0 right (normal left inverted right x axis y axis) 609mm x 349mm",
|
2023-02-09 21:22:34 +02:00
|
|
|
"LVDS-1 connected primary 1366x768+0+0 normal X axis (normal left inverted right x axis y axis) 609mm x 349mm",
|
|
|
|
"VGA-1 connected 1280x1024+0+0 left X and Y axis (normal left inverted right x axis y axis) 609mm x 349mm",
|
2022-02-02 18:25:55 -08:00
|
|
|
]
|
|
|
|
for device in devices:
|
|
|
|
self.assertIsNotNone(re.match(_device_pattern, device))
|
|
|
|
|
|
|
|
screens = [
|
|
|
|
"Screen 0: minimum 8 x 8, current 1920 x 1080, maximum 32767 x 32767",
|
2023-09-30 15:19:14 -07:00
|
|
|
"Screen 0: minimum 320 x 200, current 1920 x 1080, maximum 16384 x 16384",
|
2022-02-02 18:25:55 -08:00
|
|
|
]
|
|
|
|
for screen in screens:
|
|
|
|
self.assertIsNotNone(re.match(_screen_pattern, screen))
|
|
|
|
|
|
|
|
modes = [
|
|
|
|
"1920x1080 60.03*+ 59.93",
|
|
|
|
"1680x1050 59.88",
|
|
|
|
"1400x1050 59.98",
|
|
|
|
"1600x900 60.00 59.95 59.82",
|
|
|
|
"1280x1024 60.02",
|
|
|
|
"1400x900 59.96 59.88",
|
|
|
|
]
|
|
|
|
for mode in modes:
|
2024-02-12 09:03:25 -08:00
|
|
|
match = re.match(_resolution_mode_pattern, mode)
|
2022-02-02 18:25:55 -08:00
|
|
|
self.assertIsNotNone(match)
|
|
|
|
if match:
|
|
|
|
rest = match.groupdict()["rest"]
|
|
|
|
self.assertIsNotNone(re.match(_frequencies_pattern, rest))
|
2023-09-30 15:19:14 -07:00
|
|
|
|
2024-02-12 09:03:25 -08:00
|
|
|
def test_line_categorize(self):
|
|
|
|
base = "eDP-1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis) 309mm x 174mm"
|
|
|
|
resolution_mode = " 320x240 60.05"
|
|
|
|
prop_key = " EDID:"
|
|
|
|
prop_value = " 00ffffffffffff0006af3d5700000000"
|
|
|
|
invalid = ""
|
2023-02-17 16:25:41 +02:00
|
|
|
|
2024-02-12 15:05:44 -08:00
|
|
|
self.assertEqual(LineType.Device, _Line.categorize(base).t)
|
|
|
|
self.assertEqual(LineType.ResolutionMode, _Line.categorize(resolution_mode).t)
|
|
|
|
self.assertEqual(LineType.PropKey, _Line.categorize(prop_key).t)
|
|
|
|
self.assertEqual(LineType.PropValue, _Line.categorize(prop_value).t)
|
2024-02-12 09:03:25 -08:00
|
|
|
with self.assertRaises(Exception):
|
2024-02-12 15:05:44 -08:00
|
|
|
_Line.categorize(invalid)
|
2022-02-02 18:25:55 -08:00
|
|
|
|
|
|
|
def test_screens(self):
|
|
|
|
sample = "Screen 0: minimum 8 x 8, current 1920 x 1080, maximum 32767 x 32767"
|
2024-02-12 15:05:44 -08:00
|
|
|
line = _Line.categorize(sample)
|
2024-02-12 09:03:25 -08:00
|
|
|
actual: Optional[Screen] = _parse_screen(line)
|
2022-02-02 18:25:55 -08:00
|
|
|
self.assertIsNotNone(actual)
|
|
|
|
|
|
|
|
expected = {
|
|
|
|
"screen_number": 0,
|
|
|
|
"minimum_width": 8,
|
|
|
|
"minimum_height": 8,
|
|
|
|
"current_width": 1920,
|
|
|
|
"current_height": 1080,
|
|
|
|
"maximum_width": 32767,
|
|
|
|
"maximum_height": 32767,
|
|
|
|
}
|
|
|
|
if actual:
|
|
|
|
for k, v in expected.items():
|
|
|
|
self.assertEqual(v, actual[k], f"screens regex failed on {k}")
|
|
|
|
|
2023-09-30 15:19:14 -07:00
|
|
|
sample = (
|
|
|
|
"Screen 0: minimum 320 x 200, current 1920 x 1080, maximum 16384 x 16384"
|
|
|
|
)
|
2024-02-12 15:05:44 -08:00
|
|
|
line = _Line.categorize(sample)
|
2024-02-12 09:03:25 -08:00
|
|
|
actual = _parse_screen(line)
|
2022-02-02 18:25:55 -08:00
|
|
|
if actual:
|
|
|
|
self.assertEqual(320, actual["minimum_width"])
|
|
|
|
else:
|
|
|
|
raise AssertionError("Screen should not be None")
|
|
|
|
|
|
|
|
def test_device(self):
|
|
|
|
# regex101 sample link for tests/edits https://regex101.com/r/3cHMv3/1
|
2022-04-10 18:12:28 +09:00
|
|
|
sample = "eDP1 connected primary 1920x1080+0+0 left (normal left inverted right x axis y axis) 310mm x 170mm"
|
2024-02-12 15:05:44 -08:00
|
|
|
line = _Line.categorize(sample)
|
2024-02-12 09:03:25 -08:00
|
|
|
actual: Optional[Device] = _parse_device(line)
|
2022-02-02 18:25:55 -08:00
|
|
|
|
|
|
|
expected = {
|
|
|
|
"device_name": "eDP1",
|
|
|
|
"is_connected": True,
|
|
|
|
"is_primary": True,
|
|
|
|
"resolution_width": 1920,
|
|
|
|
"resolution_height": 1080,
|
|
|
|
"offset_width": 0,
|
|
|
|
"offset_height": 0,
|
|
|
|
"dimension_width": 310,
|
|
|
|
"dimension_height": 170,
|
2022-04-10 18:12:28 +09:00
|
|
|
"rotation": "left",
|
2022-02-02 18:25:55 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
self.assertIsNotNone(actual)
|
|
|
|
|
|
|
|
if actual:
|
|
|
|
for k, v in expected.items():
|
|
|
|
self.assertEqual(v, actual[k], f"Devices regex failed on {k}")
|
|
|
|
|
2024-02-12 09:03:25 -08:00
|
|
|
# with open("tests/fixtures/generic/xrandr_device.out", "r") as f:
|
|
|
|
# extended_sample = f.read().splitlines()
|
2022-02-02 18:25:55 -08:00
|
|
|
|
2024-02-12 09:03:25 -08:00
|
|
|
# device = _parse_device(extended_sample)
|
|
|
|
# if device:
|
|
|
|
# self.assertEqual(
|
|
|
|
# 59.94, device["resolution_modes"][12]["frequencies"][4]["frequency"]
|
|
|
|
# )
|
2022-02-02 18:25:55 -08:00
|
|
|
|
2023-02-09 21:22:34 +02:00
|
|
|
def test_device_with_reflect(self):
|
|
|
|
sample = "VGA-1 connected primary 1920x1080+0+0 left X and Y axis (normal left inverted right x axis y axis) 310mm x 170mm"
|
2024-02-12 15:05:44 -08:00
|
|
|
line = _Line.categorize(sample)
|
2024-02-12 09:03:25 -08:00
|
|
|
actual: Optional[Device] = _parse_device(line)
|
2023-02-09 21:22:34 +02:00
|
|
|
|
|
|
|
expected = {
|
|
|
|
"device_name": "VGA-1",
|
|
|
|
"is_connected": True,
|
|
|
|
"is_primary": True,
|
|
|
|
"resolution_width": 1920,
|
|
|
|
"resolution_height": 1080,
|
|
|
|
"offset_width": 0,
|
|
|
|
"offset_height": 0,
|
|
|
|
"dimension_width": 310,
|
|
|
|
"dimension_height": 170,
|
|
|
|
"rotation": "left",
|
|
|
|
"reflection": "X and Y axis",
|
|
|
|
}
|
|
|
|
|
|
|
|
self.assertIsNotNone(actual)
|
|
|
|
|
|
|
|
if actual:
|
|
|
|
for k, v in expected.items():
|
|
|
|
self.assertEqual(v, actual[k], f"Devices regex failed on {k}")
|
|
|
|
|
2022-02-02 18:25:55 -08:00
|
|
|
def test_mode(self):
|
2024-02-12 09:03:25 -08:00
|
|
|
sample_1 = " 1920x1080 60.03*+ 59.93"
|
2022-02-02 18:25:55 -08:00
|
|
|
expected = {
|
|
|
|
"frequencies": [
|
|
|
|
{"frequency": 60.03, "is_current": True, "is_preferred": True},
|
|
|
|
{"frequency": 59.93, "is_current": False, "is_preferred": False},
|
|
|
|
],
|
|
|
|
"resolution_width": 1920,
|
|
|
|
"resolution_height": 1080,
|
|
|
|
"is_high_resolution": False,
|
|
|
|
}
|
2024-02-12 15:05:44 -08:00
|
|
|
line = _Line.categorize(sample_1)
|
2024-02-12 09:03:25 -08:00
|
|
|
actual: Optional[ResolutionMode] = _parse_resolution_mode(line)
|
2022-02-02 18:25:55 -08:00
|
|
|
|
|
|
|
self.assertIsNotNone(actual)
|
|
|
|
|
|
|
|
if actual:
|
|
|
|
for k, v in expected.items():
|
|
|
|
self.assertEqual(v, actual[k], f"mode regex failed on {k}")
|
|
|
|
|
2024-02-12 09:03:25 -08:00
|
|
|
sample_2 = " 1920x1080i 60.00 50.00 59.94"
|
2024-02-12 15:05:44 -08:00
|
|
|
line = _Line.categorize(sample_2)
|
2024-02-12 09:03:25 -08:00
|
|
|
actual: Optional[ResolutionMode] = _parse_resolution_mode(line)
|
2022-02-02 18:25:55 -08:00
|
|
|
self.assertIsNotNone(actual)
|
|
|
|
if actual:
|
|
|
|
self.assertEqual(True, actual["is_high_resolution"])
|
|
|
|
self.assertEqual(50.0, actual["frequencies"][1]["frequency"])
|
|
|
|
|
2023-09-30 15:19:14 -07:00
|
|
|
def test_complete_1(self):
|
2022-02-02 18:25:55 -08:00
|
|
|
self.maxDiff = None
|
|
|
|
with open("tests/fixtures/generic/xrandr.out", "r") as f:
|
2024-03-23 10:57:46 -07:00
|
|
|
actual = parse(f.read(), quiet=True)
|
|
|
|
|
|
|
|
with open('tests/fixtures/generic/xrandr.json', 'r') as f:
|
|
|
|
reference = json.loads(f.read())
|
2022-02-02 18:25:55 -08:00
|
|
|
|
|
|
|
self.assertEqual(1, len(actual["screens"]))
|
2024-02-12 09:03:25 -08:00
|
|
|
self.assertEqual(
|
|
|
|
18, len(actual["screens"][0]["devices"][0]["resolution_modes"])
|
|
|
|
)
|
2024-03-23 10:57:46 -07:00
|
|
|
self.assertEqual(actual, reference)
|
2022-02-02 18:25:55 -08:00
|
|
|
|
2023-09-30 15:19:14 -07:00
|
|
|
def test_complete_2(self):
|
2022-02-02 18:25:55 -08:00
|
|
|
with open("tests/fixtures/generic/xrandr_2.out", "r") as f:
|
2024-03-23 10:57:46 -07:00
|
|
|
actual = parse(f.read(), quiet=True)
|
|
|
|
|
|
|
|
with open('tests/fixtures/generic/xrandr_2.json', 'r') as f:
|
|
|
|
reference = json.loads(f.read())
|
2022-02-02 18:25:55 -08:00
|
|
|
|
|
|
|
self.assertEqual(1, len(actual["screens"]))
|
2024-02-12 09:03:25 -08:00
|
|
|
self.assertEqual(
|
|
|
|
38, len(actual["screens"][0]["devices"][0]["resolution_modes"])
|
|
|
|
)
|
2024-03-23 10:57:46 -07:00
|
|
|
self.assertEqual(actual, reference)
|
2022-02-02 18:25:55 -08:00
|
|
|
|
2023-09-30 15:19:14 -07:00
|
|
|
def test_complete_3(self):
|
|
|
|
with open("tests/fixtures/generic/xrandr_3.out", "r") as f:
|
2024-03-23 10:57:46 -07:00
|
|
|
actual = parse(f.read(), quiet=True)
|
|
|
|
|
|
|
|
with open('tests/fixtures/generic/xrandr_3.json', 'r') as f:
|
|
|
|
reference = json.loads(f.read())
|
2022-02-02 18:25:55 -08:00
|
|
|
|
|
|
|
self.assertEqual(1, len(actual["screens"]))
|
|
|
|
self.assertEqual(
|
2023-09-30 15:19:14 -07:00
|
|
|
2,
|
|
|
|
len(actual["screens"][0]["devices"]),
|
2022-02-02 18:25:55 -08:00
|
|
|
)
|
2024-03-23 10:57:46 -07:00
|
|
|
self.assertEqual(actual, reference)
|
2022-02-14 09:14:38 -08:00
|
|
|
|
2023-09-30 15:19:14 -07:00
|
|
|
def test_complete_4(self):
|
|
|
|
with open("tests/fixtures/generic/xrandr_simple.out", "r") as f:
|
2024-03-23 10:57:46 -07:00
|
|
|
actual = parse(f.read(), quiet=True)
|
|
|
|
|
|
|
|
with open('tests/fixtures/generic/xrandr_simple.json', 'r') as f:
|
|
|
|
reference = json.loads(f.read())
|
2023-02-17 16:25:41 +02:00
|
|
|
|
|
|
|
self.assertEqual(1, len(actual["screens"]))
|
2024-02-12 09:03:25 -08:00
|
|
|
self.assertEqual(2, len(actual["screens"][0]["devices"][0]["resolution_modes"]))
|
2024-03-23 10:57:46 -07:00
|
|
|
self.assertEqual(actual, reference)
|
2023-02-13 18:09:10 -08:00
|
|
|
|
2023-09-30 15:19:14 -07:00
|
|
|
def test_complete_5(self):
|
2024-02-12 09:03:25 -08:00
|
|
|
with open("tests/fixtures/generic/xrandr_properties_1.out", "r") as f:
|
2024-03-23 10:57:46 -07:00
|
|
|
actual = parse(f.read(), quiet=True)
|
|
|
|
|
|
|
|
with open('tests/fixtures/generic/xrandr_properties_1.json', 'r') as f:
|
|
|
|
reference = json.loads(f.read())
|
2023-02-13 18:17:14 -08:00
|
|
|
|
2023-09-30 15:19:14 -07:00
|
|
|
self.assertEqual(1, len(actual["screens"]))
|
2024-02-12 09:03:25 -08:00
|
|
|
self.assertEqual(
|
|
|
|
38, len(actual["screens"][0]["devices"][0]["resolution_modes"])
|
|
|
|
)
|
2024-03-23 10:57:46 -07:00
|
|
|
self.assertEqual(actual, reference)
|
2023-02-17 16:25:41 +02:00
|
|
|
|
2024-02-12 09:03:25 -08:00
|
|
|
# def test_model(self):
|
|
|
|
# asus_edid = [
|
|
|
|
# " EDID: ",
|
|
|
|
# " 00ffffffffffff000469d41901010101",
|
|
|
|
# " 2011010308291a78ea8585a6574a9c26",
|
|
|
|
# " 125054bfef80714f8100810f81408180",
|
|
|
|
# " 9500950f01019a29a0d0518422305098",
|
|
|
|
# " 360098ff1000001c000000fd00374b1e",
|
|
|
|
# " 530f000a202020202020000000fc0041",
|
|
|
|
# " 535553205657313933530a20000000ff",
|
|
|
|
# " 0037384c383032313130370a20200077",
|
|
|
|
# ]
|
|
|
|
# asus_edid.reverse()
|
|
|
|
|
|
|
|
# expected = {
|
|
|
|
# "name": "ASUS VW193S",
|
|
|
|
# "product_id": "6612",
|
|
|
|
# "serial_number": "78L8021107",
|
|
|
|
# }
|
|
|
|
|
|
|
|
# actual: Optional[EdidModel] = _parse_model(asus_edid)
|
|
|
|
# self.assertIsNotNone(actual)
|
|
|
|
|
|
|
|
# if actual:
|
|
|
|
# for k, v in expected.items():
|
|
|
|
# self.assertEqual(v, actual[k], f"mode regex failed on {k}")
|
|
|
|
|
|
|
|
# generic_edid = [
|
|
|
|
# " EDID: ",
|
|
|
|
# " 00ffffffffffff004ca3523100000000",
|
|
|
|
# " 0014010380221378eac8959e57549226",
|
|
|
|
# " 0f505400000001010101010101010101",
|
|
|
|
# " 010101010101381d56d4500016303020",
|
|
|
|
# " 250058c2100000190000000f00000000",
|
|
|
|
# " 000000000025d9066a00000000fe0053",
|
|
|
|
# " 414d53554e470a204ca34154000000fe",
|
|
|
|
# " 004c544e313536415432343430310018",
|
|
|
|
# ]
|
|
|
|
# generic_edid.reverse()
|
|
|
|
|
|
|
|
# expected = {
|
|
|
|
# "name": "Generic",
|
|
|
|
# "product_id": "12626",
|
|
|
|
# "serial_number": "0",
|
|
|
|
# }
|
|
|
|
|
|
|
|
# jc.parsers.xrandr.parse_state = {}
|
|
|
|
# actual: Optional[EdidModel] = _parse_model(generic_edid)
|
|
|
|
# self.assertIsNotNone(actual)
|
|
|
|
|
|
|
|
# if actual:
|
|
|
|
# for k, v in expected.items():
|
|
|
|
# self.assertEqual(v, actual[k], f"mode regex failed on {k}")
|
|
|
|
|
|
|
|
# empty_edid = [""]
|
|
|
|
# actual: Optional[EdidModel] = _parse_model(empty_edid)
|
|
|
|
# self.assertIsNone(actual)
|
2023-09-30 15:19:14 -07:00
|
|
|
|
2023-12-16 11:39:40 -08:00
|
|
|
def test_issue_490(self):
|
|
|
|
"""test for issue 490: https://github.com/kellyjonbrazil/jc/issues/490"""
|
2024-02-12 09:03:25 -08:00
|
|
|
data_in = """\
|
2023-12-16 11:39:40 -08:00
|
|
|
Screen 0: minimum 1024 x 600, current 1024 x 600, maximum 1024 x 600
|
|
|
|
default connected 1024x600+0+0 0mm x 0mm
|
|
|
|
1024x600 0.00*
|
2024-02-12 09:03:25 -08:00
|
|
|
"""
|
|
|
|
actual: Response = parse(data_in)
|
|
|
|
self.maxDiff = None
|
|
|
|
self.assertEqual(1024, actual["screens"][0]["devices"][0]["resolution_width"])
|
|
|
|
|
|
|
|
def test_issue_525(self):
|
|
|
|
self.maxDiff = None
|
|
|
|
with open("tests/fixtures/generic/xrandr_issue_525.out", "r") as f:
|
|
|
|
txt = f.read()
|
|
|
|
actual = parse(txt, quiet=True)
|
|
|
|
dp4 = actual["screens"][0]["devices"][0]["props"]["Broadcast RGB"][1] # type: ignore
|
|
|
|
# pprint.pprint(actual)
|
|
|
|
self.assertEqual("supported: Automatic, Full, Limited 16:235", dp4)
|
|
|
|
edp1_expected_keys = {
|
|
|
|
"EDID",
|
|
|
|
"EdidModel",
|
|
|
|
"scaling mode",
|
|
|
|
"Colorspace",
|
|
|
|
"max bpc",
|
|
|
|
"Broadcast RGB",
|
|
|
|
"panel orientation",
|
|
|
|
"link-status",
|
|
|
|
"CTM",
|
|
|
|
"CONNECTOR_ID",
|
|
|
|
"non-desktop",
|
|
|
|
}
|
|
|
|
actual_keys = set(actual["screens"][0]["devices"][0]["props"].keys())
|
|
|
|
self.assertSetEqual(edp1_expected_keys, actual_keys)
|
|
|
|
expected_edid_model = {
|
|
|
|
"name": "Generic",
|
|
|
|
"product_id": "22333",
|
|
|
|
"serial_number": "0",
|
|
|
|
}
|
|
|
|
self.assertDictEqual(
|
|
|
|
expected_edid_model,
|
|
|
|
actual["screens"][0]["devices"][0]["props"]["EdidModel"], # type: ignore
|
|
|
|
)
|
2023-12-16 11:39:40 -08:00
|
|
|
|
2024-03-23 10:57:46 -07:00
|
|
|
def test_issue_549(self):
|
|
|
|
"""https://github.com/kellyjonbrazil/jc/issues/549"""
|
|
|
|
with open("tests/fixtures/generic/xrandr_extra_hv_lines.out", "r") as f:
|
|
|
|
actual = parse(f.read(), quiet=True)
|
|
|
|
|
|
|
|
with open('tests/fixtures/generic/xrandr_extra_hv_lines.json', 'r') as f:
|
|
|
|
reference = json.loads(f.read())
|
|
|
|
|
|
|
|
self.assertEqual(actual, reference)
|
|
|
|
|
2023-12-16 11:39:40 -08:00
|
|
|
|
2023-09-30 15:19:14 -07:00
|
|
|
if __name__ == "__main__":
|
2022-04-10 18:12:28 +09:00
|
|
|
unittest.main()
|