Merge ~pieq/plainbox-provider-checkbox:1968943-rework-max-resolution-job into plainbox-provider-checkbox:master

Proposed by Pierre Equoy
Status: Merged
Approved by: Sylvain Pineau
Approved revision: a028a44bb18f0f2e9e26c91c3e7f216c70911509
Merged at revision: 91478e881f1672cb9c9a3f68fd7b2d31b6602b07
Proposed branch: ~pieq/plainbox-provider-checkbox:1968943-rework-max-resolution-job
Merge into: plainbox-provider-checkbox:master
Diff against target: 289 lines (+149/-99)
4 files modified
bin/graphics_max_resolution.py (+148/-0)
dev/null (+0/-74)
units/graphics/jobs.pxu (+1/-17)
units/graphics/legacy.pxu (+0/-8)
Reviewer Review Type Date Requested Status
Devices Certification Bot Needs Fixing
Sylvain Pineau (community) Approve
Review via email: mp+424146@code.launchpad.net

Description of the change

See commit messages for more information.

This change provides more information when running the .*maximum_resolution.* Checkbox job, and is compatible with 20.04 and 22.04, on X11 and Wayland.

These changes have been tested on 20.04 and 22.04 using X11 and Wayland on two different devices:
- a laptop with a UHD screen (3840x2160) and an external FHD monitor (1920x1080) connected to it
- a desktop with a UHD monitor (3840x2160) connected to it

I tested a bunch of different scenarios:

- different resolutions
- different scale factors
- different GNOME settings ("Join Displays", "Mirror", "Single Display")

The results are available here:

https://pastebin.ubuntu.com/p/r5xB5PfZtJ/

Pay attention to the output as well as the choice selected by default in Checkbox (passed/failed) which reflects the script outcome.

Additionally, this MR also remove an unused script and job from the provider, since they are not used in any of our test plans.

To post a comment you must log in.
Revision history for this message
Sylvain Pineau (sylvain-pineau) wrote :

tested as well on 20.04 and 22.04. all good.

But I doubt flake8 will let the last long line to land!

review: Needs Fixing
Revision history for this message
Pierre Equoy (pieq) wrote :

Argh. I ran black on it, I thought that was enough...

Anyway, I reformatted and made sure it passes flake8.

Revision history for this message
Sylvain Pineau (sylvain-pineau) wrote :

thx, +1

review: Approve
Revision history for this message
Devices Certification Bot (ce-certification-qa) wrote :

The merge was fine but running tests failed.

"10.38.105.108"
"10.38.105.54"
"10.38.105.197"
[bionic] [09:42:36] starting container
[xenial] [09:42:37] starting container
Device project added to bionic-testing
Device project added to xenial-testing
"10.38.105.54"
[xenial] [09:42:56] provisioning container
"10.38.105.51"
[bionic] [09:42:58] provisioning container
[focal] [09:43:09] starting container
Device project added to focal-testing
"10.38.105.60"
[focal] [09:43:34] provisioning container
[xenial] [09:44:32] Starting tests...
[xenial] Found a test script: ./requirements/container-tests-provider-checkbox
[bionic] [09:44:41] Starting tests...
[bionic] Found a test script: ./requirements/container-tests-provider-checkbox
[xenial] [09:45:54] container-tests-provider-checkbox: FAIL
[xenial] output: https://paste.ubuntu.com/p/wJwZVrvfkV/
[xenial] [09:45:57] Fixing file permissions in source directory
[xenial] [09:45:57] Destroying container
[bionic] [09:46:55] container-tests-provider-checkbox: PASS
[bionic] [09:46:55] Fixing file permissions in source directory
[bionic] [09:46:55] Destroying container
[focal] [09:48:13] Starting tests...
[focal] Found a test script: ./requirements/container-tests-provider-checkbox
[focal] [09:49:41] container-tests-provider-checkbox: PASS
[focal] [09:49:41] Fixing file permissions in source directory
[focal] [09:49:41] Destroying container

review: Needs Fixing
Revision history for this message
Pierre Equoy (pieq) wrote :

Xenial didn't like the f-strings formatting...

Modified this, ran black on 20.04, then flake8 on 20.04 and 18.04, looks good.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/bin/graphics_max_resolution.py b/bin/graphics_max_resolution.py
0new file mode 1007550new file mode 100755
index 0000000..56628a4
--- /dev/null
+++ b/bin/graphics_max_resolution.py
@@ -0,0 +1,148 @@
1#!/usr/bin/env python3
2#
3# This file is part of Checkbox.
4#
5# Copyright 2022 Canonical Ltd.
6#
7# Authors:
8# Pierre Equoy <pierre.equoy@canonical.com>
9#
10# Checkbox is free software: you can redistribute it and/or modify
11# it under the terms of the GNU General Public License version 3,
12# as published by the Free Software Foundation.
13#
14# Checkbox is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
21
22import gi
23from glob import glob
24import os
25import sys
26from pathlib import Path
27
28gi.require_versions({"Gtk": "3.0", "Gdk": "3.0"})
29from gi.repository import Gdk, Gtk # noqa: E402
30
31
32def get_sysfs_info():
33 """
34 Go through each graphics cards sysfs entries to find max resolution if
35 connected to a monitor.
36 Return a list of ports with information about them.
37 """
38 ports = glob("/sys/class/drm/card*-*")
39 entries = []
40 for p in ports:
41 with open(Path(p) / "modes") as f:
42 # Topmost line in the modes file is the max resolution
43 max_resolution = f.readline().strip()
44 if max_resolution:
45 # e.g. "/sys/class/drm/card0-HDMI-A-1"
46 port = p.split("/")[-1]
47 width, height = max_resolution.split("x")
48 with open(Path(p) / "enabled") as f:
49 enabled = f.readline().strip()
50 with open(Path(p) / "dpms") as f:
51 dpms = f.readline().strip()
52 with open(Path(p) / "status") as f:
53 status = f.readline().strip()
54 port_info = {
55 "port": port,
56 "width": int(width),
57 "height": int(height),
58 "enabled": enabled, # "enabled" or "disabled"
59 "status": status, # "connected" or "disconnected"
60 "dpms": dpms, # "On" or "Off"
61 }
62 entries.append(port_info)
63 return entries
64
65
66def get_monitors_info():
67 """
68 Get information (model, manufacturer, resolution) from each connected
69 monitors using Gtk.
70 Return a list of monitors with their information.
71 """
72 Gtk.init()
73 display = Gdk.Display.get_default()
74 monitors = []
75 for i in range(display.get_n_monitors()):
76 mon = display.get_monitor(i)
77 monitor = {
78 "model": mon.get_model(),
79 "manufacturer": mon.get_manufacturer(),
80 "width": mon.get_geometry().width,
81 "height": mon.get_geometry().height,
82 "scale_factor": mon.get_scale_factor(),
83 }
84 monitors.append(monitor)
85 return monitors
86
87
88if __name__ == "__main__":
89 sysfs_entries = get_sysfs_info()
90 mons_entries = get_monitors_info()
91 total_sysfs_res = 0
92 total_mons_res = 0
93 compositor = os.environ.get("XDG_SESSION_TYPE")
94 print("Current compositor: {}".format(compositor))
95 print()
96 print("Maximum resolution found for each connected monitors:")
97 for p in sysfs_entries:
98 port = p["port"]
99 width = p["width"]
100 height = p["height"]
101 enabled = p["enabled"]
102 status = p["status"]
103 dpms = p["dpms"].lower()
104 print(
105 "\t{}: {}x{} ({}, {}, {})".format(
106 port, width, height, dpms, status, enabled
107 )
108 )
109 # If the monitor is disabled (e.g. "Single Display" mode), don't take
110 # its surface into account.
111 if enabled == "enabled":
112 total_sysfs_res += width * height
113 print()
114 print("Current resolution found for each connected monitors:")
115 for m in mons_entries:
116 model = m["model"]
117 manufacturer = m["manufacturer"]
118 scale = m["scale_factor"]
119 # Under X11, the returned width and height are in "application pixels",
120 # not "device pixels", so it has to be multiplied by the scale factor.
121 # However, Wayland always returns the "device pixels" width and height.
122 #
123 # Example: a 3840x2160 screen set to 200% scale will have
124 # width = 1920, height = 1080, scale_factor = 2 on X11
125 # width = 3840, height = 2160, scale_factor = 2 on Wayland
126 if compositor == "x11":
127 width = m["width"] * m["scale_factor"]
128 height = m["height"] * m["scale_factor"]
129 else:
130 width = m["width"]
131 height = m["height"]
132 print(
133 "\t{} ({}): {}x{} @{}%".format(
134 model, manufacturer, width, height, scale * 100
135 )
136 )
137 total_mons_res += width * height
138 print()
139 if total_sysfs_res == total_mons_res:
140 print("The displays are configured at their maximum resolution.")
141 else:
142 sys.exit(
143 (
144 "The displays do not seem to be configured at their maximum "
145 "resolution.\nPlease switch to the maximum resolution before "
146 "continuing."
147 )
148 )
diff --git a/bin/graphics_modes_info.py b/bin/graphics_modes_info.py
0deleted file mode 100755149deleted file mode 100755
index f92cd76..0000000
--- a/bin/graphics_modes_info.py
+++ /dev/null
@@ -1,74 +0,0 @@
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3#
4# graphics_modes_info.py
5#
6# This file is part of Checkbox.
7#
8# Copyright 2012 Canonical Ltd.
9#
10# Authors: Alberto Milone <alberto.milone@canonical.com>
11#
12# Checkbox is free software: you can redistribute it and/or modify
13# it under the terms of the GNU General Public License version 3,
14# as published by the Free Software Foundation.
15
16#
17# Checkbox is distributed in the hope that it will be useful,
18# but WITHOUT ANY WARRANTY; without even the implied warranty of
19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20# GNU General Public License for more details.
21#
22# You should have received a copy of the GNU General Public License
23# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
24
25from __future__ import print_function
26from __future__ import unicode_literals
27import sys
28
29from checkbox_support.contrib import xrandr
30
31
32def print_modes_info(screen):
33 """Print some information about the detected screen and its outputs"""
34 xrandr._check_required_version((1, 0))
35 print("Screen %s: minimum %s x %s, current %s x %s, maximum %s x %s" %
36 (screen._screen,
37 screen._width_min, screen._height_min,
38 screen._width, screen._height,
39 screen._width_max, screen._height_max))
40 print(" %smm x %smm" % (screen._width_mm, screen._height_mm))
41 print("Outputs:")
42 for o in list(screen.outputs.keys()):
43 output = screen.outputs[o]
44 print(" %s" % o, end=' ')
45 if output.is_connected():
46 print("(%smm x %smm)" % (output.get_physical_width(),
47 output.get_physical_height()))
48 modes = output.get_available_modes()
49 print(" Modes:")
50 for m in range(len(modes)):
51 mode = modes[m]
52 refresh = mode.dotClock / (mode.hTotal * mode.vTotal)
53 print(
54 " [%s] %s x %s @ %s Hz" %
55 (m, mode.width, mode.height, refresh), end=' ')
56 if mode.id == output._mode:
57 print("(current)", end=' ')
58 if m == output.get_preferred_mode():
59 print("(preferred)", end=' ')
60 print("")
61 else:
62 print("(not connected)")
63
64
65def main():
66 screen = xrandr.get_current_screen()
67 try:
68 print_modes_info(screen)
69 except(xrandr.UnsupportedRRError):
70 print('Error: RandR version lower than 1.0', file=sys.stderr)
71
72
73if __name__ == '__main__':
74 main()
diff --git a/units/graphics/jobs.pxu b/units/graphics/jobs.pxu
index 4dde304..8206f1a 100644
--- a/units/graphics/jobs.pxu
+++ b/units/graphics/jobs.pxu
@@ -133,13 +133,7 @@ category_id: com.canonical.plainbox::graphics
133requires:133requires:
134 device.category == 'VIDEO'134 device.category == 'VIDEO'
135command:135command:
136 # shellcheck disable=SC1091136 graphics_max_resolution.py
137 source graphics_env.sh {driver} {index}
138 maxi="$(xrandr -q |grep -A 1 "connected\( primary\)* [0-9]" |tail -1 |awk '{{print $1}}')"
139 now="$(python3 -c 'from gi.repository import Gdk; screen=Gdk.Screen.get_default(); geo = screen.get_monitor_geometry(screen.get_primary_monitor()); print(geo.width, "x", geo.height, sep="")')"
140 test "$maxi" != "$now" && notify="\nPlease switch to the maximum resolution \nfor every graphic tests"
141 echo "Maximum resolution: $maxi"
142 echo "Current resolution: $now $notify"
143estimated_duration: 10.0137estimated_duration: 10.0
144_summary: Test maximum supported resolution for {vendor} {product}138_summary: Test maximum supported resolution for {vendor} {product}
145_description:139_description:
@@ -154,16 +148,6 @@ _description:
154148
155unit: template149unit: template
156template-resource: graphics_card150template-resource: graphics_card
157id: graphics/{index}_modes_{product_slug}
158plugin: shell
159category_id: com.canonical.plainbox::graphics
160command: graphics_modes_info.py
161estimated_duration: 0.250
162_description: Collect info on graphics modes (screen resolution and refresh rate) for {vendor} {product}
163_summary: Test graphic modes info for {vendor} {product}
164
165unit: template
166template-resource: graphics_card
167id: graphics/{index}_color_depth_{product_slug}151id: graphics/{index}_color_depth_{product_slug}
168plugin: shell152plugin: shell
169category_id: com.canonical.plainbox::graphics153category_id: com.canonical.plainbox::graphics
diff --git a/units/graphics/legacy.pxu b/units/graphics/legacy.pxu
index 14969b0..2b9545d 100644
--- a/units/graphics/legacy.pxu
+++ b/units/graphics/legacy.pxu
@@ -96,14 +96,6 @@ _description:
96 VERIFICATION:96 VERIFICATION:
97 Is this the display's maximum resolution?97 Is this the display's maximum resolution?
9898
99id: graphics/modes
100plugin: shell
101category_id: com.canonical.plainbox::graphics
102command: graphics_modes_info.py
103estimated_duration: 0.250
104_description: Collect info on graphics modes (screen resolution and refresh rate)
105_summary: Collect info on graphics modes
106
107id: graphics/color_depth99id: graphics/color_depth
108plugin: shell100plugin: shell
109category_id: com.canonical.plainbox::graphics101category_id: com.canonical.plainbox::graphics

Subscribers

People subscribed via source and target branches