1 # Test printing via CUPS.
3 import ./make-test-python.nix (
5 , socket ? true # whether to use socket activation
6 , listenTcp ? true # whether to open port 631 on client
16 meta = with lib.maintainers; {
17 maintainers = [ domenkozar matthewbauer ];
20 nodes.server = { ... }: {
24 startWhenNeeded = socket;
25 listenAddresses = [ "*:631" ];
35 # Add a HP Deskjet printer connected via USB to the server.
36 hardware.printers.ensurePrinters = [{
37 name = "DeskjetLocal";
38 deviceUri = "usb://foobar/printers/foobar";
39 model = "drv:///sample.drv/deskjet.ppd";
43 nodes.client = { lib, ... }: {
44 services.printing.enable = true;
45 services.printing.startWhenNeeded = socket;
46 services.printing.listenAddresses = lib.mkIf (!listenTcp) [];
47 # Add printer to the client as well, via IPP.
48 hardware.printers.ensurePrinters = [{
49 name = "DeskjetRemote";
50 deviceUri = "ipp://server/printers/DeskjetLocal";
51 model = "drv:///sample.drv/deskjet.ppd";
53 hardware.printers.ensureDefaultPrinter = "DeskjetRemote";
62 with subtest("Make sure that cups is up on both sides and printers are set up"):
63 server.wait_for_unit("ensure-printers.service")
64 client.wait_for_unit("ensure-printers.service")
66 assert "scheduler is running" in client.succeed("lpstat -r")
68 with subtest("UNIX socket is used for connections"):
69 assert "/var/run/cups/cups.sock" in client.succeed("lpstat -H")
71 with subtest("HTTP server is available too"):
72 ${lib.optionalString listenTcp ''client.succeed("curl --fail http://localhost:631/")''}
73 client.succeed(f"curl --fail http://{server.name}:631/")
74 server.fail(f"curl --fail --connect-timeout 2 http://{client.name}:631/")
76 with subtest("LP status checks"):
77 assert "DeskjetRemote accepting requests" in client.succeed("lpstat -a")
78 assert "DeskjetLocal accepting requests" in client.succeed(
79 f"lpstat -h {server.name}:631 -a"
81 client.succeed("cupsdisable DeskjetRemote")
82 out = client.succeed("lpq")
85 "DeskjetRemote is not ready.*no entries",
86 client.succeed("lpq"),
89 client.succeed("cupsenable DeskjetRemote")
91 "DeskjetRemote is ready.*no entries", client.succeed("lpq"), flags=re.DOTALL
94 # Test printing various file types.
96 "${pkgs.groff.doc}/share/doc/*/examples/mom/penguin.pdf",
97 "${pkgs.groff.doc}/share/doc/*/meref.ps",
98 "${pkgs.cups.out}/share/doc/cups/images/cups.png",
99 "${pkgs.pcre.doc}/share/doc/pcre/pcre.txt",
101 file_name = os.path.basename(file)
102 with subtest(f"print {file_name}"):
103 # Print the file on the client.
104 print(client.succeed("lpq"))
105 client.succeed(f"lp {file}")
106 client.wait_until_succeeds(
107 f"lpq; lpq | grep -q -E 'active.*root.*{file_name}'"
110 # Ensure that a raw PCL file appeared in the server's queue
111 # (showing that the right filters have been applied). Of
112 # course, since there is no actual USB printer attached, the
113 # file will stay in the queue forever.
114 server.wait_for_file("/var/spool/cups/d*-001")
115 server.wait_until_succeeds(f"lpq -a | grep -q -E '{file_name}'")
117 # Delete the job on the client. It should disappear on the
119 client.succeed("lprm")
120 client.wait_until_succeeds("lpq -a | grep -q -E 'no entries'")
122 retry(lambda _: "no entries" in server.succeed("lpq -a"))
124 # The queue is empty already, so this should be safe.
125 # Otherwise, pairs of "c*"-"d*-001" files might persist.
126 server.execute("rm /var/spool/cups/*")