1# Test printing via CUPS.
2{
3 pkgs,
4 socket ? true, # whether to use socket activation
5 listenTcp ? true, # whether to open port 631 on client
6 ...
7}:
8
9let
10 inherit (pkgs) lib;
11in
12
13{
14 name = "printing";
15 meta = with lib.maintainers; {
16 maintainers = [
17 domenkozar
18 matthewbauer
19 ];
20 };
21
22 nodes.server =
23 { ... }:
24 {
25 services.printing = {
26 enable = true;
27 stateless = true;
28 startWhenNeeded = socket;
29 listenAddresses = [ "*:631" ];
30 defaultShared = true;
31 openFirewall = true;
32 extraConf = ''
33 <Location />
34 Order allow,deny
35 Allow from all
36 </Location>
37 '';
38 };
39 # Add a HP Deskjet printer connected via USB to the server.
40 hardware.printers.ensurePrinters = [
41 {
42 name = "DeskjetLocal";
43 deviceUri = "usb://foobar/printers/foobar";
44 model = "drv:///sample.drv/deskjet.ppd";
45 }
46 ];
47 };
48
49 nodes.client =
50 { lib, ... }:
51 {
52 services.printing.enable = true;
53 services.printing.startWhenNeeded = socket;
54 services.printing.listenAddresses = lib.mkIf (!listenTcp) [ ];
55 # Add printer to the client as well, via IPP.
56 hardware.printers.ensurePrinters = [
57 {
58 name = "DeskjetRemote";
59 deviceUri = "ipp://server/printers/DeskjetLocal";
60 model = "drv:///sample.drv/deskjet.ppd";
61 }
62 ];
63 hardware.printers.ensureDefaultPrinter = "DeskjetRemote";
64 };
65
66 testScript = ''
67 import os
68 import re
69
70 start_all()
71
72 with subtest("Make sure that cups is up on both sides and printers are set up"):
73 server.wait_for_unit("ensure-printers.service")
74 client.wait_for_unit("ensure-printers.service")
75
76 assert "scheduler is running" in client.succeed("lpstat -r")
77
78 with subtest("UNIX socket is used for connections"):
79 assert "/var/run/cups/cups.sock" in client.succeed("lpstat -H")
80
81 with subtest("HTTP server is available too"):
82 ${lib.optionalString listenTcp ''client.succeed("curl --fail http://localhost:631/")''}
83 client.succeed(f"curl --fail http://{server.name}:631/")
84 server.fail(f"curl --fail --connect-timeout 2 http://{client.name}:631/")
85
86 with subtest("LP status checks"):
87 assert "DeskjetRemote accepting requests" in client.succeed("lpstat -a")
88 assert "DeskjetLocal accepting requests" in client.succeed(
89 f"lpstat -h {server.name}:631 -a"
90 )
91 client.succeed("cupsdisable DeskjetRemote")
92 out = client.succeed("lpq")
93 print(out)
94 assert re.search(
95 "DeskjetRemote is not ready.*no entries",
96 client.succeed("lpq"),
97 flags=re.DOTALL,
98 )
99 client.succeed("cupsenable DeskjetRemote")
100 assert re.match(
101 "DeskjetRemote is ready.*no entries", client.succeed("lpq"), flags=re.DOTALL
102 )
103
104 # Test printing various file types.
105 for file in [
106 "${pkgs.groff.doc}/share/doc/*/examples/mom/penguin.pdf",
107 "${pkgs.groff.doc}/share/doc/*/meref.ps",
108 "${pkgs.cups.out}/share/doc/cups/images/cups.png",
109 "${pkgs.pcre.doc}/share/doc/pcre/pcre.txt",
110 ]:
111 file_name = os.path.basename(file)
112 with subtest(f"print {file_name}"):
113 # Print the file on the client.
114 print(client.succeed("lpq"))
115 client.succeed(f"lp {file}")
116 client.wait_until_succeeds(
117 f"lpq; lpq | grep -q -E 'active.*root.*{file_name}'"
118 )
119
120 # Ensure that a raw PCL file appeared in the server's queue
121 # (showing that the right filters have been applied). Of
122 # course, since there is no actual USB printer attached, the
123 # file will stay in the queue forever.
124 server.wait_for_file("/var/spool/cups/d*-001")
125 server.wait_until_succeeds(f"lpq -a | grep -q -E '{file_name}'")
126
127 # Delete the job on the client. It should disappear on the
128 # server as well.
129 client.succeed("lprm")
130 client.wait_until_succeeds("lpq -a | grep -q -E 'no entries'")
131
132 retry(lambda _: "no entries" in server.succeed("lpq -a"))
133
134 # The queue is empty already, so this should be safe.
135 # Otherwise, pairs of "c*"-"d*-001" files might persist.
136 server.execute("rm /var/spool/cups/*")
137 '';
138}