Skip to content

Commit 38b29e2

Browse files
Add printer test (New) (#2276)
* Add script to find printer by input keyword and try to print out the test string * Rebase and fix conflict * Fix black format * Modfiy the jobs id * Add somemore logic to handle different printer modle If the URI with IPP then use model everywhere. If the URI with USB then use model raw * Make automated test to check device only since the paper for the printer could be running out in the long term maintenance cycle * Fix black format * Fix missing quotation mark for the variable PRINTER_MODEL * Change the phrase of the arg explanation * Handle timeout by using function run_with_timeout from checkbox-support * Fix black format * Add data type for the functions * Fix conflict * Update contrib/checkbox-ce-oem/checkbox-provider-ce-oem/bin/printer_test.py Co-authored-by: stanley31huang <stanley.huang@canonical.com> * Fix the typo of variable * Change the job purpose for automated test --------- Co-authored-by: stanley31huang <stanley.huang@canonical.com>
1 parent b488acd commit 38b29e2

File tree

6 files changed

+261
-0
lines changed

6 files changed

+261
-0
lines changed
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
#!/usr/bin/env python3
2+
# This script rely on user input the brand name of the printer
3+
# as the keyword to searching connected printer.
4+
import argparse
5+
import logging
6+
import subprocess
7+
import time
8+
import re
9+
from checkbox_support.helpers.timeout import run_with_timeout
10+
11+
12+
def run_command(command: str):
13+
"""Executes a shell command and returns the output."""
14+
try:
15+
result = subprocess.run(
16+
command,
17+
shell=True,
18+
check=True,
19+
capture_output=True,
20+
text=True,
21+
)
22+
return result.stdout.strip()
23+
except subprocess.CalledProcessError as e:
24+
logging.error("Error executing command: {}".format(e.stderr))
25+
return None
26+
27+
28+
def find_printer_uri(keyword: str):
29+
"""Scans lpinfo for a printer matching the keyword."""
30+
logging.info("--- Searching for printer matching: {} ---".format(keyword))
31+
output = run_command("lpinfo -v")
32+
if not output:
33+
return None
34+
35+
# Look for the specific URI line
36+
for line in output.split("\n"):
37+
match = re.search(
38+
r"\S+://\S*{}\S*".format(re.escape(keyword)),
39+
line,
40+
re.IGNORECASE,
41+
)
42+
if match:
43+
return match.group(0)
44+
return None
45+
46+
47+
def monitor_job(printer_name: str, job_id: str):
48+
"""Checks the status of the job."""
49+
logging.info("--- Monitoring Job {} ---".format(job_id))
50+
51+
while True:
52+
# Check if job is in the 'completed' list
53+
completed_jobs = run_command(
54+
"lpstat -W completed -o {}".format(printer_name)
55+
)
56+
if job_id in completed_jobs:
57+
logging.info(
58+
"SUCCESS: Job {} has been COMPLETED by the printer.".format(
59+
job_id
60+
)
61+
)
62+
return
63+
64+
# Check if it's still in the active queue
65+
active_jobs = run_command("lpq -P {}".format(printer_name))
66+
if job_id not in active_jobs and job_id in completed_jobs:
67+
logging.info("SUCCESS: Job {} finished.".format(job_id))
68+
return
69+
70+
logging.info("Job pending/processing... checking again in 5s")
71+
time.sleep(5)
72+
73+
74+
def teardown_printer(printer_name: str):
75+
"""Removes the printer configuration."""
76+
logging.info("Removing printer '{}'...".format(printer_name))
77+
run_command("lpadmin -x {}".format(printer_name))
78+
79+
80+
def main():
81+
logging.basicConfig(
82+
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
83+
)
84+
parser = argparse.ArgumentParser(description="Printer Test Script")
85+
parser.add_argument(
86+
"-k",
87+
"--keyword",
88+
required=True,
89+
type=str,
90+
help="Keyword to search for printer URI",
91+
)
92+
parser.add_argument(
93+
"-m",
94+
"--model",
95+
type=str,
96+
help="Target model to link up",
97+
)
98+
parser.add_argument(
99+
"--check-device",
100+
action="store_true",
101+
help="Only check if the device is present the URI only",
102+
)
103+
args = parser.parse_args()
104+
keyword = args.keyword
105+
printer_name = "Printer_Tester"
106+
107+
# 1. Find URI
108+
uri = find_printer_uri(keyword)
109+
if not uri:
110+
logging.error(
111+
"Could not find a printer with keyword '{}'.".format(keyword)
112+
)
113+
raise SystemExit(1)
114+
115+
logging.info("Found URI: {}".format(uri))
116+
117+
if args.check_device:
118+
return
119+
120+
driver_opt = ""
121+
if args.model:
122+
driver_opt = "-m {}".format(args.model)
123+
elif "usb://" in uri:
124+
driver_opt = "-m raw"
125+
elif "ipp://" in uri:
126+
driver_opt = "-m everywhere"
127+
128+
# 2. Create/Link the printer
129+
logging.info("Linking printer as '{}'...".format(printer_name))
130+
if (
131+
run_command(
132+
"lpadmin -p {} -v '{}' -E {}".format(printer_name, uri, driver_opt)
133+
)
134+
is None
135+
):
136+
logging.error("Failed to link printer.")
137+
raise SystemExit(1)
138+
139+
# 3. Print a test page
140+
logging.info("Sending test print job...")
141+
test_text = "Printer TEST\nSTATUS: LINKED\n"
142+
print_success = True
143+
try:
144+
lp_output = run_command(
145+
"echo '{}' | lp -d {}".format(test_text, printer_name)
146+
)
147+
148+
if lp_output and "request id is" in lp_output:
149+
# Extract Job ID (e.g., Printer test_Queue-10)
150+
job_id = lp_output.split(" ")[3]
151+
logging.info("Job submitted: {}".format(job_id))
152+
153+
# 4. Monitor status
154+
run_with_timeout(monitor_job, 30, printer_name, job_id)
155+
else:
156+
print_success = False
157+
logging.error("Failed to submit print job.")
158+
except TimeoutError:
159+
print_success = False
160+
logging.error(
161+
"TIMEOUT: Job did not reach 'completed' status within "
162+
"30 seconds."
163+
)
164+
finally:
165+
teardown_printer(printer_name)
166+
if not print_success:
167+
raise SystemExit(1)
168+
169+
170+
if __name__ == "__main__":
171+
main()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
unit: category
2+
id: printer
3+
_name: Printer Test
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
id: ce-oem/printer-test-automated
2+
_summary: Test printer functionality
3+
_purpose:
4+
This autotest verifies that the printer listed in CUPS service.
5+
plugin: shell
6+
user: root
7+
category_id: printer
8+
estimated_duration: 25s
9+
flags: also-after-suspend
10+
command: printer_test.py -k "$PRINTER_BRAND" -m "$PRINTER_MODEL" --check-device
11+
imports:
12+
from com.canonical.plainbox import manifest
13+
from com.canonical.certification import package
14+
requires:
15+
manifest.has_printer == 'True'
16+
package.name == 'cups-client'
17+
environ: PRINTER_BRAND PRINTER_MODEL
18+
19+
id: ce-oem/printer-test
20+
_summary: Test printer functionality
21+
_purpose:
22+
This test verifies that the printer is correctly installed and can print a test page.
23+
plugin: user-interact-verify
24+
user: root
25+
category_id: printer
26+
estimated_duration: 25s
27+
flags: also-after-suspend
28+
command: printer_test.py -k "$PRINTER_BRAND" -m "$PRINTER_MODEL"
29+
imports:
30+
from com.canonical.plainbox import manifest
31+
from com.canonical.certification import package
32+
requires:
33+
manifest.has_printer == 'True'
34+
package.name == 'cups-client'
35+
environ: PRINTER_BRAND PRINTER_MODEL
36+
_steps:
37+
1. Press enter and wait the test string printed
38+
_verification:
39+
Do you see the test string printed out?
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
unit: manifest entry
2+
id: has_printer
3+
_name: Does the platform have a printer?
4+
value-type: bool
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
id: ce-oem-printer-full
2+
unit: test plan
3+
_name: Printer tests
4+
_description: Full printer tests for devices
5+
include:
6+
nested_part:
7+
ce-oem-printer-manual
8+
ce-oem-printer-automated
9+
after-suspend-ce-oem-printer-manual
10+
after-suspend-ce-oem-printer-automated
11+
12+
id: ce-oem-printer-manual
13+
unit: test plan
14+
_name: Printer manual tests
15+
_description: Manual printer tests for devices
16+
bootstrap_include:
17+
include:
18+
ce-oem/printer-test
19+
20+
id: ce-oem-printer-automated
21+
unit: test plan
22+
_name: Printer auto tests
23+
_description: Automated printer tests for devices
24+
include:
25+
ce-oem/printer-test-automated
26+
27+
id: after-suspend-ce-oem-printer-manual
28+
unit: test plan
29+
_name: After suspend printer manual tests
30+
_description: Manual after suspend printer tests for devices
31+
bootstrap_include:
32+
include:
33+
after-suspend-ce-oem/printer-test
34+
35+
id: after-suspend-ce-oem-printer-automated
36+
unit: test plan
37+
_name: After suspend printer auto tests
38+
_description: Automated after suspend printer tests for devices
39+
include:
40+
after-suspend-ce-oem/printer-test-automated

contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/test-plan-ce-oem.pxu

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ nested_part:
5353
ce-oem-graphics-manual
5454
ce-oem-rpmsg-manual
5555
ce-oem-barcode-scanner-manual
56+
ce-oem-printer-manual
5657
certification_status_overrides:
5758
apply blocker to .*
5859

@@ -102,6 +103,7 @@ nested_part:
102103
ce-oem-video-codec-automated
103104
ce-oem-rpmsg-automated
104105
ce-oem-barcode-scanner-automated
106+
ce-oem-printer-automated
105107
certification_status_overrides:
106108
apply blocker to .*
107109

@@ -136,6 +138,7 @@ nested_part:
136138
after-suspend-ce-oem-mir-automated
137139
after-suspend-ce-oem-regulator-manual
138140
after-suspend-ce-oem-barcode-scanner-manual
141+
after-suspend-ce-oem-printer-manual
139142
certification_status_overrides:
140143
apply blocker to .*
141144

@@ -173,6 +176,7 @@ nested_part:
173176
after-suspend-ce-oem-spi-automated
174177
after-suspend-ce-oem-regulator-automated
175178
after-suspend-ce-oem-barcode-scanner-automated
179+
after-suspend-ce-oem-printer-automated
176180
certification_status_overrides:
177181
apply blocker to .*
178182

0 commit comments

Comments
 (0)