QtPass 1.6.0
Multi-platform GUI for pass, the standard unix password manager.
Loading...
Searching...
No Matches
tst_executor.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Anne Jan Brouwer
2// SPDX-License-Identifier: GPL-3.0-or-later
3#include <QtTest>
4
6#include "../../../src/pass.h"
8
9class tst_executor : public QObject {
10 Q_OBJECT
11
12private Q_SLOTS:
13#ifndef Q_OS_WIN
14 void executeBlockingEcho();
15 void executeBlockingWithArgs();
16 void executeBlockingWithInput();
17 void executeBlockingExitCode();
18 void executeBlockingStderr();
19 void executeBlockingEmptyArgs();
20 void executeBlockingEchoMultiple();
21 void executeBlockingSpecialChars();
22#endif
23 void executeBlockingNotFound();
24 void executeBlockingGpgVersion();
25 void gpgSupportsEd25519();
26 void getDefaultKeyTemplate();
27 void executeBlockingGpgKillAgent();
28 void resolveGpgconfCommand();
29};
30
31#ifndef Q_OS_WIN
32void tst_executor::executeBlockingEcho() {
33 QString output;
34 int result = Executor::executeBlocking("echo", {"hello"}, QString(), &output);
35 QVERIFY2(result == 0, "echo should exit successfully");
36 QVERIFY2(output.contains("hello"), "output should contain 'hello'");
37}
38
39void tst_executor::executeBlockingWithArgs() {
40 QString output;
41 int result =
42 Executor::executeBlocking("echo", {"hello", "world"}, QString(), &output);
43 QVERIFY2(result == 0, "echo should exit successfully");
44 QVERIFY2(output.contains("hello world"), "output should contain both args");
45}
46
47void tst_executor::executeBlockingWithInput() {
48 QString output;
49 QString input = "test input";
50 int result = Executor::executeBlocking("cat", {}, input, &output);
51 QVERIFY2(result == 0, "cat should exit successfully");
52 QVERIFY2(output.contains("test input"), "output should echo input");
53}
54
55void tst_executor::executeBlockingExitCode() {
56 QString output;
57 int result = Executor::executeBlocking("false", {}, QString(), &output);
58 QVERIFY2(result != 0, "false should exit with non-zero");
59}
60
61void tst_executor::executeBlockingStderr() {
62 QString output;
63 QString err;
64 Executor::executeBlocking("sh", {"-c", "echo error >&2"}, QString(), &output,
65 &err);
66 QVERIFY2(err.contains("error"), "stderr should contain error");
67}
68
69void tst_executor::executeBlockingEmptyArgs() {
70 QString output;
71 int result = Executor::executeBlocking("echo", {}, QString(), &output);
72 QVERIFY2(result == 0, "echo with empty args should succeed");
73}
74
75void tst_executor::executeBlockingEchoMultiple() {
76 QString output;
77 int result = Executor::executeBlocking("sh", {"-c", "echo a && echo b"},
78 QString(), &output);
79 QVERIFY2(result == 0, "shell should exit successfully");
80 QVERIFY(output.contains("a"));
81 QVERIFY(output.contains("b"));
82}
83
84void tst_executor::executeBlockingSpecialChars() {
85 QString output;
86 int result = Executor::executeBlocking("echo", {"$PATH"}, QString(), &output);
87 QVERIFY2(result == 0, "echo should succeed");
88 QVERIFY2(output.trimmed() == "$PATH", "literal $PATH should be preserved");
89}
90#endif
91
92void tst_executor::executeBlockingNotFound() {
93 QString output;
94 int result = Executor::executeBlocking("nonexistent_command_xyz", {},
95 QString(), &output);
96 QVERIFY2(result != 0, "nonexistent should fail");
97}
98
99void tst_executor::executeBlockingGpgVersion() {
100 QString output;
101 QString err;
102 int result =
103 Executor::executeBlocking("gpg", {"--version"}, QString(), &output, &err);
104 if (result != 0) {
105 QSKIP("gpg not available");
106 }
107 QVERIFY2(output.contains("gpg"), "output should contain gpg");
108}
109
110void tst_executor::gpgSupportsEd25519() {
111 QString output;
112 QString err;
113 int result =
114 Executor::executeBlocking("gpg", {"--version"}, QString(), &output, &err);
115 if (result != 0) {
116 QSKIP("gpg not available");
117 }
118 bool supported = Pass::gpgSupportsEd25519();
119 QRegularExpression versionRegex(R"(gpg \‍(GnuPG\) (\d+)\.(\d+))");
120 QRegularExpressionMatch match = versionRegex.match(output);
121 QVERIFY2(match.hasMatch(), "Could not parse gpg version output");
122 int major = match.captured(1).toInt();
123 int minor = match.captured(2).toInt();
124 bool expectedSupported = major > 2 || (major == 2 && minor >= 1);
125 if (supported != expectedSupported) {
126 QSKIP("GPG version mismatch between test and Pass::gpgSupportsEd25519");
127 }
128}
129
130void tst_executor::getDefaultKeyTemplate() {
131 QString templateStr = Pass::getDefaultKeyTemplate();
132 QVERIFY2(!templateStr.isEmpty(), "Default key template should not be empty");
133 QVERIFY2(templateStr.contains("Key-Type"),
134 "Template should contain Key-Type");
135}
136
137void tst_executor::executeBlockingGpgKillAgent() {
138#ifndef Q_OS_WIN
139 QString output;
140 QString err;
141 int result = Executor::executeBlocking("gpgconf", {"--kill", "gpg-agent"},
142 QString(), &output, &err);
143 if (result != 0) {
144 QSKIP("gpgconf not available in PATH");
145 }
146 QVERIFY2(result == 0, "gpgconf --kill gpg-agent should succeed");
147#else
148 QSKIP("gpgconf not available on Windows");
149#endif
150}
151
152void tst_executor::resolveGpgconfCommand() {
153 // Empty input
154 {
155 auto result = Pass::resolveGpgconfCommand("");
156 QVERIFY2(result.program == "gpgconf",
157 "Empty input should fallback to gpgconf");
158 }
159
160 // WSL simple
161 {
162 auto result = Pass::resolveGpgconfCommand("wsl gpg2");
163 QStringList expectedArgs = {"gpgconf"};
164 QVERIFY2(result.program == "wsl" && result.arguments == expectedArgs,
165 "WSL simple should replace gpg with gpgconf");
166 }
167
168 // WSL with distro
169 {
170 auto result = Pass::resolveGpgconfCommand("wsl --distro Debian gpg2");
171 QVERIFY2(result.program == "wsl", "WSL distro preserves wsl");
172 QVERIFY2(result.arguments.contains("--distro") &&
173 result.arguments.contains("Debian"),
174 "WSL distro arguments should be preserved");
175 }
176
177 // WSL with full path
178 {
179 auto result = Pass::resolveGpgconfCommand("wsl /usr/bin/gpg2");
180 QStringList expectedArgs = {"/usr/bin/gpgconf"};
181 QVERIFY2(result.program == "wsl" && result.arguments == expectedArgs,
182 "WSL with full path should preserve directory");
183 }
184
185 // WSL complex (should fallback)
186 {
187 auto result = Pass::resolveGpgconfCommand("wsl sh -c \"gpg2 --version\"");
188 QVERIFY2(result.program == "gpgconf", "Complex WSL shell should fallback");
189 }
190
191 // WSL malformed (only "wsl")
192 {
193 auto result = Pass::resolveGpgconfCommand("wsl");
194 QVERIFY2(result.program == "gpgconf", "Malformed WSL should fallback");
195 }
196
197 // PATH-only
198 {
199 auto result = Pass::resolveGpgconfCommand("gpg2");
200 QVERIFY2(result.program == "gpgconf" && result.arguments.isEmpty(),
201 "PATH-only should fallback with no extra arguments");
202 }
203
204 // Unix absolute - use temp directory for filesystem-independent test
205 {
206 QTemporaryDir tempDir;
207 QVERIFY2(tempDir.isValid(), "Temp directory should be valid");
208 QString absPath = tempDir.path() + "/gpg2";
209 QFile gpg2File(absPath);
210 QVERIFY2(gpg2File.open(QIODevice::WriteOnly),
211 "Should be able to create temporary gpg2 file");
212 gpg2File.close();
213 auto result = Pass::resolveGpgconfCommand(absPath);
214 QVERIFY2(result.program == "gpgconf", "Absolute path should fallback");
215 }
216}
217
218QTEST_MAIN(tst_executor)
219#include "tst_executor.moc"
static auto executeBlocking(QString app, const QStringList &args, const QString &input=QString(), QString *process_out=nullptr, QString *process_err=nullptr) -> int
Executor::executeBlocking blocking version of the executor, takes input and presents it as stdin.
Definition executor.cpp:223
static bool gpgSupportsEd25519()
Check if GPG supports Ed25519 encryption.
Definition pass.cpp:157
static auto resolveGpgconfCommand(const QString &gpgPath) -> ResolvedGpgconfCommand
Resolve the gpgconf command to kill agents.
Definition pass.cpp:322
static QString getDefaultKeyTemplate()
Get default key template for new GPG keys.
Definition pass.cpp:178