Line data Source code
1 : /**
2 : * @file test_compose_pty.c
3 : * @brief PTY tests for email-tui compose/reply/send commands.
4 : *
5 : * Usage: test-pty-compose <email-tui-bin> <mock-smtp-server-bin> <email-cli-bin>
6 : */
7 :
8 : #include "ptytest.h"
9 : #include "pty_assert.h"
10 : #include <stdio.h>
11 : #include <stdlib.h>
12 : #include <string.h>
13 : #include <unistd.h>
14 : #include <signal.h>
15 : #include <fcntl.h>
16 : #include <sys/stat.h>
17 : #include <sys/wait.h>
18 : #include <sys/socket.h>
19 : #include <arpa/inet.h>
20 : #include <errno.h>
21 :
22 : /* ── Test infrastructure ─────────────────────────────────────────────── */
23 :
24 : #define COLS 120
25 : #define ROWS 50
26 : #define WAIT_MS 4000
27 : #define SETTLE_MS 300
28 :
29 : static int g_tests_run = 0;
30 : static int g_tests_failed = 0;
31 :
32 : static char g_tui_bin[512];
33 : static char g_cli_bin[512];
34 : static char g_smtp_mock_bin[512];
35 : static char g_test_home[512];
36 : static pid_t g_smtp_pid = -1;
37 :
38 : #define RUN_TEST(fn) \
39 : do { \
40 : fprintf(stdout, " %s...\n", #fn); \
41 : fflush(stdout); \
42 : fn(); \
43 : } while(0)
44 :
45 : /* ── Config helpers ──────────────────────────────────────────────────── */
46 :
47 90 : static void write_config(void) {
48 : char d1[600], d2[620], d3[640], d4[660], path[700];
49 90 : snprintf(d1, sizeof(d1), "%s/.config", g_test_home);
50 90 : snprintf(d2, sizeof(d2), "%s/.config/email-cli", g_test_home);
51 90 : snprintf(d3, sizeof(d3), "%s/.config/email-cli/accounts", g_test_home);
52 90 : snprintf(d4, sizeof(d4), "%s/.config/email-cli/accounts/testuser", g_test_home);
53 90 : mkdir(g_test_home, 0700);
54 90 : mkdir(d1, 0700);
55 90 : mkdir(d2, 0700);
56 90 : mkdir(d3, 0700);
57 90 : mkdir(d4, 0700);
58 90 : snprintf(path, sizeof(path), "%s/config.ini", d4);
59 90 : FILE *fp = fopen(path, "w");
60 90 : if (!fp) return;
61 90 : fprintf(fp,
62 : "EMAIL_HOST=imaps://localhost:9993\n"
63 : "EMAIL_USER=testuser\n"
64 : "EMAIL_PASS=testpass\n"
65 : "EMAIL_FOLDER=INBOX\n"
66 : "SMTP_HOST=smtps://localhost:9025\n"
67 : "SMTP_PORT=9025\n"
68 : "SMTP_USER=testuser\n"
69 : "SMTP_PASS=testpass\n"
70 : "SSL_NO_VERIFY=1\n"); /* permit self-signed cert from mock server */
71 90 : fclose(fp);
72 90 : chmod(path, 0600);
73 : }
74 :
75 : /* ── Mock SMTP server management ──────────────────────────────────────── */
76 :
77 57 : static int probe_smtp(void) {
78 57 : int fd = socket(AF_INET, SOCK_STREAM, 0);
79 57 : if (fd < 0) return -1;
80 57 : struct sockaddr_in addr = {
81 : .sin_family = AF_INET,
82 57 : .sin_port = htons(9025),
83 57 : .sin_addr.s_addr = htonl(INADDR_LOOPBACK)
84 : };
85 57 : int ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
86 57 : close(fd);
87 57 : return ret;
88 : }
89 :
90 57 : static void start_smtp_server(void) {
91 57 : g_smtp_pid = fork();
92 63 : if (g_smtp_pid < 0) return;
93 63 : if (g_smtp_pid == 0) {
94 6 : int devnull = open("/dev/null", O_WRONLY);
95 6 : if (devnull >= 0) { dup2(devnull, 2); close(devnull); }
96 6 : execl(g_smtp_mock_bin, "mock_smtp_server", (char *)NULL);
97 6 : _exit(127);
98 : }
99 57 : usleep(600000);
100 : }
101 :
102 41 : static void restart_smtp(void) {
103 41 : if (g_smtp_pid > 0) {
104 41 : kill(g_smtp_pid, SIGKILL);
105 41 : waitpid(g_smtp_pid, NULL, 0);
106 41 : g_smtp_pid = -1;
107 : }
108 41 : usleep(200000);
109 41 : start_smtp_server();
110 41 : for (int i = 0; i < 30 && probe_smtp() != 0; i++)
111 0 : usleep(100000);
112 41 : }
113 :
114 1 : static void stop_smtp_server(void) {
115 1 : if (g_smtp_pid > 0) {
116 1 : kill(g_smtp_pid, SIGKILL);
117 1 : waitpid(g_smtp_pid, NULL, 0);
118 1 : g_smtp_pid = -1;
119 : }
120 1 : }
121 :
122 : /* ── Runner helper ───────────────────────────────────────────────────── */
123 :
124 126 : static PtySession *tui_open_size(int cols, int rows, const char **extra_args) {
125 : const char *args[32];
126 126 : int n = 0;
127 126 : args[n++] = g_tui_bin;
128 126 : if (extra_args)
129 534 : for (int i = 0; extra_args[i] && n < 31; i++)
130 408 : args[n++] = extra_args[i];
131 126 : args[n] = NULL;
132 :
133 126 : PtySession *s = pty_open(cols, rows);
134 126 : if (!s) return NULL;
135 126 : if (pty_run(s, args) != 0) { pty_close(s); return NULL; }
136 114 : return s;
137 : }
138 :
139 58 : static PtySession *tui_run(const char **extra_args) {
140 58 : return tui_open_size(COLS, ROWS, extra_args);
141 : }
142 :
143 9 : static PtySession *cli_open_size(int cols, int rows, const char **extra_args) {
144 : const char *args[32];
145 9 : int n = 0;
146 9 : args[n++] = g_cli_bin;
147 9 : if (extra_args)
148 29 : for (int i = 0; extra_args[i] && n < 31; i++)
149 20 : args[n++] = extra_args[i];
150 9 : args[n] = NULL;
151 :
152 9 : PtySession *s = pty_open(cols, rows);
153 9 : if (!s) return NULL;
154 9 : if (pty_run(s, args) != 0) { pty_close(s); return NULL; }
155 6 : return s;
156 : }
157 :
158 4 : static PtySession *cli_run(const char **extra_args) {
159 4 : return cli_open_size(COLS, ROWS, extra_args);
160 : }
161 :
162 : /* ── Help page tests ─────────────────────────────────────────────────── */
163 :
164 16 : static void test_help_compose(void) {
165 16 : const char *a[] = {"compose", "--help", NULL};
166 16 : PtySession *s = tui_open_size(120, 50, a);
167 15 : ASSERT(s != NULL, "help compose: opens");
168 15 : ASSERT_WAIT_FOR(s, "Usage: email-tui", WAIT_MS);
169 15 : pty_settle(s, SETTLE_MS);
170 15 : ASSERT_SCREEN_CONTAINS(s, "To");
171 15 : ASSERT_SCREEN_CONTAINS(s, "Subject");
172 15 : pty_close(s);
173 : }
174 :
175 15 : static void test_help_send(void) {
176 15 : const char *a[] = {"send", "--help", NULL};
177 15 : PtySession *s = tui_open_size(120, 50, a);
178 14 : ASSERT(s != NULL, "help send: opens");
179 14 : ASSERT_WAIT_FOR(s, "Usage: email-tui", WAIT_MS);
180 14 : pty_settle(s, SETTLE_MS);
181 14 : ASSERT_SCREEN_CONTAINS(s, "--to");
182 14 : ASSERT_SCREEN_CONTAINS(s, "--subject");
183 14 : ASSERT_SCREEN_CONTAINS(s, "--body");
184 14 : pty_close(s);
185 : }
186 :
187 14 : static void test_help_reply(void) {
188 14 : const char *a[] = {"reply", "--help", NULL};
189 14 : PtySession *s = tui_open_size(120, 50, a);
190 13 : ASSERT(s != NULL, "help reply: opens");
191 13 : ASSERT_WAIT_FOR(s, "Usage: email-tui", WAIT_MS);
192 13 : pty_settle(s, SETTLE_MS);
193 13 : ASSERT_SCREEN_CONTAINS(s, "<uid>");
194 13 : pty_close(s);
195 : }
196 :
197 : /* ── Batch send tests ──────────────────────────────────────────────────── */
198 :
199 13 : static void test_send_missing_to(void) {
200 13 : write_config();
201 13 : const char *a[] = {"--batch", "send", "--subject", "Hi", "--body", "Hello", NULL};
202 13 : PtySession *s = tui_open_size(120, 50, a);
203 12 : ASSERT(s != NULL, "send missing to: opens");
204 12 : ASSERT_WAIT_FOR(s, "required", WAIT_MS);
205 12 : pty_close(s);
206 : }
207 :
208 12 : static void test_send_batch_ok(void) {
209 12 : write_config();
210 12 : restart_smtp();
211 12 : const char *a[] = {"--batch", "send",
212 : "--to", "recipient@example.com",
213 : "--subject", "Test Subject",
214 : "--body", "Hello from PTY test",
215 : NULL};
216 12 : PtySession *s = tui_run(a);
217 11 : ASSERT(s != NULL, "batch send: opens");
218 11 : ASSERT_WAIT_FOR(s, "Message sent", WAIT_MS);
219 11 : pty_close(s);
220 : }
221 :
222 11 : static void test_send_no_smtp_config(void) {
223 : /* Write config without SMTP settings */
224 : char d1[600], d2[620], d3[640], d4[660], path[700];
225 11 : snprintf(d1, sizeof(d1), "%s/.config", g_test_home);
226 11 : snprintf(d2, sizeof(d2), "%s/.config/email-cli", g_test_home);
227 11 : snprintf(d3, sizeof(d3), "%s/.config/email-cli/accounts", g_test_home);
228 11 : snprintf(d4, sizeof(d4), "%s/.config/email-cli/accounts/testuser", g_test_home);
229 11 : mkdir(g_test_home, 0700); mkdir(d1, 0700); mkdir(d2, 0700);
230 11 : mkdir(d3, 0700); mkdir(d4, 0700);
231 11 : snprintf(path, sizeof(path), "%s/config.ini", d4);
232 11 : FILE *fp = fopen(path, "w");
233 11 : if (fp) {
234 11 : fprintf(fp,
235 : "EMAIL_HOST=imaps://localhost:9993\n"
236 : "EMAIL_USER=testuser\n"
237 : "EMAIL_PASS=testpass\n"
238 : "EMAIL_FOLDER=INBOX\n"
239 : "SSL_NO_VERIFY=1\n"); /* permit non-TLS mock server in tests */
240 11 : fclose(fp);
241 11 : chmod(path, 0600);
242 : }
243 11 : const char *a[] = {"--batch", "send",
244 : "--to", "x@x.com", "--subject", "X", "--body", "X", NULL};
245 11 : PtySession *s = tui_run(a);
246 10 : ASSERT(s != NULL, "no smtp cfg: opens");
247 : /* Should still attempt; derives SMTP from IMAP host */
248 : /* We just check it exits (doesn't hang) */
249 10 : pty_settle(s, SETTLE_MS * 5);
250 10 : pty_close(s);
251 : }
252 :
253 : /* ── Compose TUI tests (editor-based) ───────────────────────────────── */
254 :
255 : /**
256 : * Helper: write a shell script that replaces the draft file ($1) with a
257 : * complete message and returns. Sets $EDITOR to that script path.
258 : * Returns the script path (caller must unlink when done).
259 : */
260 : static char g_editor_script[256];
261 :
262 24 : static void setup_editor_mock(const char *from, const char *to,
263 : const char *subject, const char *body) {
264 24 : snprintf(g_editor_script, sizeof(g_editor_script),
265 24 : "/tmp/test_editor_%d.sh", (int)getpid());
266 24 : FILE *ef = fopen(g_editor_script, "w");
267 24 : if (!ef) return;
268 24 : fprintf(ef, "#!/bin/sh\n");
269 24 : fprintf(ef,
270 : "printf 'From: %s\\nTo: %s\\nSubject: %s\\n\\n%s\\n' > \"$1\"\n",
271 : from, to, subject, body);
272 24 : fclose(ef);
273 24 : chmod(g_editor_script, 0755);
274 24 : setenv("EDITOR", g_editor_script, 1);
275 : }
276 :
277 21 : static void cleanup_editor_mock(void) {
278 21 : unlink(g_editor_script);
279 21 : g_editor_script[0] = '\0';
280 21 : }
281 :
282 : /**
283 : * compose abort: EDITOR=true → editor exits without changing To: → "Aborted"
284 : */
285 10 : static void test_compose_abort_no_to(void) {
286 10 : write_config();
287 10 : setenv("EDITOR", "true", 1); /* no-op editor: exits without saving */
288 10 : const char *a[] = {"compose", NULL};
289 10 : PtySession *s = tui_open_size(120, 50, a);
290 9 : ASSERT(s != NULL, "compose abort no-to: opens");
291 : /* US-85: the editor is followed by the review screen, not an immediate
292 : * "Aborted"; discarding there is what ends the compose. */
293 9 : ASSERT_WAIT_FOR(s, "Review: Message", WAIT_MS);
294 9 : pty_send_str(s, "q");
295 9 : ASSERT_WAIT_FOR(s, "Discard draft?", WAIT_MS);
296 9 : pty_send_str(s, "y");
297 9 : ASSERT_WAIT_FOR(s, "Cancelled. Draft discarded.", WAIT_MS);
298 9 : pty_close(s);
299 : }
300 :
301 : /**
302 : * compose success: mock editor fills in all headers + body → confirm 'y' → "Message sent"
303 : */
304 9 : static void test_compose_editor_send(void) {
305 9 : write_config();
306 9 : restart_smtp();
307 9 : setup_editor_mock("testuser@example.com", "recipient@example.com",
308 : "PTY test subject", "Hello from editor mock test");
309 9 : const char *a[] = {"compose", NULL};
310 9 : PtySession *s = tui_run(a);
311 8 : ASSERT(s != NULL, "compose editor send: opens");
312 8 : ASSERT_WAIT_FOR(s, "Review: Message", WAIT_MS * 2);
313 8 : pty_send_str(s, "s");
314 8 : ASSERT_WAIT_FOR(s, "Message sent", WAIT_MS);
315 8 : pty_close(s);
316 8 : cleanup_editor_mock();
317 : }
318 :
319 : /**
320 : * compose cancel: mock editor fills in all headers + body → confirm 'n' → "Cancelled"
321 : */
322 8 : static void test_compose_confirm_cancel(void) {
323 8 : write_config();
324 8 : restart_smtp();
325 8 : setup_editor_mock("testuser@example.com", "recipient@example.com",
326 : "Cancelled test subject", "Body of cancelled message");
327 8 : const char *a[] = {"compose", NULL};
328 8 : PtySession *s = tui_run(a);
329 7 : ASSERT(s != NULL, "compose confirm cancel: opens");
330 7 : ASSERT_WAIT_FOR(s, "Review: Message", WAIT_MS * 2);
331 7 : pty_send_str(s, "q");
332 7 : ASSERT_WAIT_FOR(s, "Discard draft?", WAIT_MS);
333 7 : pty_send_str(s, "y");
334 7 : ASSERT_WAIT_FOR(s, "Cancelled", WAIT_MS);
335 7 : pty_close(s);
336 7 : cleanup_editor_mock();
337 : }
338 :
339 : /**
340 : * \r stripping (US-20): reply quote must not contain carriage-return bytes.
341 : *
342 : * We set up an editor that captures the draft to a temp file for inspection.
343 : * The mock IMAP server delivers messages with CRLF line endings (RFC 2822);
344 : * after quote extraction those CRs must be stripped before the draft is
345 : * written. The editor mock simply outputs a fixed reply so the send
346 : * completes; we inspect the captured draft file instead of the sent message.
347 : */
348 5 : static void test_reply_no_cr_in_quote(void) {
349 5 : write_config();
350 5 : restart_smtp();
351 :
352 : /* Editor mock: capture draft to a known path, then fill in a valid reply */
353 : char capture_path[256];
354 5 : snprintf(capture_path, sizeof(capture_path),
355 5 : "/tmp/draft_capture_%d.txt", (int)getpid());
356 :
357 : char editor_script[300];
358 5 : snprintf(editor_script, sizeof(editor_script),
359 5 : "/tmp/test_editor_cr_%d.sh", (int)getpid());
360 5 : FILE *ef = fopen(editor_script, "w");
361 5 : if (ef) {
362 5 : fprintf(ef, "#!/bin/sh\n");
363 : /* Copy draft to capture path for inspection */
364 5 : fprintf(ef, "cp \"$1\" '%s'\n", capture_path);
365 : /* Fill valid To: so compose sends */
366 5 : fprintf(ef,
367 : "printf 'From: testuser@example.com\\n"
368 : "To: recipient@example.com\\n"
369 : "Subject: Re: Test\\n\\nReply body\\n' > \"$1\"\n");
370 5 : fclose(ef);
371 5 : chmod(editor_script, 0755);
372 : }
373 5 : setenv("EDITOR", editor_script, 1);
374 :
375 : /* Launch as batch reply UID 1 (mock server has message UID 1) */
376 5 : const char *a[] = {"reply", "1", NULL};
377 5 : PtySession *s = tui_run(a);
378 4 : ASSERT(s != NULL, "no CR in quote: opens");
379 : /* Wait until the editor has run and the draft was captured */
380 4 : pty_settle(s, WAIT_MS);
381 4 : pty_close(s);
382 :
383 : /* Inspect captured draft: must not contain \r */
384 4 : FILE *fp = fopen(capture_path, "r");
385 4 : if (fp) {
386 0 : int found_cr = 0;
387 : int c;
388 0 : while ((c = fgetc(fp)) != EOF)
389 0 : if (c == '\r') { found_cr = 1; break; }
390 0 : fclose(fp);
391 0 : ASSERT(!found_cr, "no CR in quoted body: draft must not contain \\r");
392 0 : unlink(capture_path);
393 : }
394 : /* cleanup */
395 4 : unlink(editor_script);
396 : }
397 :
398 : /**
399 : * reply abort: batch reply with EDITOR=true → empty To: → "Aborted"
400 : * (cmd_reply is invoked; the raw message may not exist → error before editor)
401 : */
402 6 : static void test_reply_missing_uid(void) {
403 6 : write_config();
404 6 : const char *a[] = {"--batch", "reply", NULL};
405 6 : PtySession *s = tui_run(a);
406 5 : ASSERT(s != NULL, "reply missing uid: opens");
407 5 : ASSERT_WAIT_FOR(s, "requires", WAIT_MS);
408 5 : pty_close(s);
409 : }
410 :
411 : /**
412 : * compose sent folder (US-20): after a successful send, "Saving to Sent folder"
413 : * appears in output confirming the sent copy is stored locally/remotely.
414 : */
415 7 : static void test_compose_sent_folder(void) {
416 7 : write_config();
417 7 : restart_smtp();
418 7 : setup_editor_mock("testuser@example.com", "recipient@example.com",
419 : "Sent folder test", "Body for sent folder test");
420 7 : const char *a[] = {"compose", NULL};
421 7 : PtySession *s = tui_run(a);
422 6 : ASSERT(s != NULL, "compose sent folder: opens");
423 6 : ASSERT_WAIT_FOR(s, "Review: Message", WAIT_MS * 2);
424 6 : pty_send_str(s, "s");
425 6 : ASSERT_WAIT_FOR(s, "Saved locally", WAIT_MS);
426 6 : pty_close(s);
427 6 : cleanup_editor_mock();
428 : }
429 :
430 : /* ── Config command tests (US-24) ────────────────────────────────────── */
431 :
432 : /**
433 : * config show: batch mode prints IMAP settings including user and folder.
434 : * Passwords are masked; the account user and folder must be visible.
435 : */
436 4 : static void test_config_show(void) {
437 4 : write_config();
438 4 : const char *a[] = {"config", "show", NULL};
439 4 : PtySession *s = cli_run(a);
440 3 : ASSERT(s != NULL, "config show: opens");
441 3 : ASSERT_WAIT_FOR(s, "IMAP", WAIT_MS);
442 3 : pty_settle(s, SETTLE_MS);
443 3 : ASSERT_SCREEN_CONTAINS(s, "testuser");
444 3 : ASSERT_SCREEN_CONTAINS(s, "INBOX");
445 3 : pty_close(s);
446 : }
447 :
448 : /**
449 : * config --help: must mention imap, smtp, and --account subcommands.
450 : */
451 3 : static void test_config_help(void) {
452 3 : const char *a[] = {"config", "--help", NULL};
453 3 : PtySession *s = cli_open_size(120, 50, a);
454 2 : ASSERT(s != NULL, "config help: opens");
455 2 : ASSERT_WAIT_FOR(s, "Usage: email-cli", WAIT_MS);
456 2 : pty_settle(s, SETTLE_MS);
457 2 : ASSERT_SCREEN_CONTAINS(s, "imap");
458 2 : ASSERT_SCREEN_CONTAINS(s, "smtp");
459 2 : ASSERT_SCREEN_CONTAINS(s, "account");
460 2 : pty_close(s);
461 : }
462 :
463 : /**
464 : * config imap --help: must print usage information and exit.
465 : */
466 2 : static void test_config_imap_help(void) {
467 2 : const char *a[] = {"config", "imap", "--help", NULL};
468 2 : PtySession *s = cli_open_size(120, 50, a);
469 1 : ASSERT(s != NULL, "config imap help: opens");
470 1 : ASSERT_WAIT_FOR(s, "Usage: email-cli", WAIT_MS);
471 1 : pty_settle(s, SETTLE_MS);
472 1 : ASSERT_SCREEN_CONTAINS(s, "imap");
473 1 : pty_close(s);
474 : }
475 :
476 : /* ── Main ────────────────────────────────────────────────────────────── */
477 :
478 16 : int main(int argc, char *argv[]) {
479 16 : if (argc < 4) {
480 0 : fprintf(stderr, "Usage: %s <email-tui> <mock-smtp-server> <email-cli>\n", argv[0]);
481 0 : return 1;
482 : }
483 :
484 16 : snprintf(g_tui_bin, sizeof(g_tui_bin), "%s", argv[1]);
485 16 : snprintf(g_smtp_mock_bin, sizeof(g_smtp_mock_bin), "%s", argv[2]);
486 16 : snprintf(g_cli_bin, sizeof(g_cli_bin), "%s", argv[3]);
487 :
488 : /* Isolated HOME for tests */
489 16 : snprintf(g_test_home, sizeof(g_test_home), "/tmp/email_cli_compose_test_%d", getpid());
490 16 : mkdir(g_test_home, 0700);
491 16 : setenv("HOME", g_test_home, 1);
492 16 : unsetenv("XDG_CONFIG_HOME");
493 16 : unsetenv("XDG_CACHE_HOME");
494 16 : unsetenv("XDG_DATA_HOME");
495 :
496 16 : write_config();
497 16 : start_smtp_server();
498 16 : for (int i = 0; i < 30 && probe_smtp() != 0; i++) usleep(100000);
499 :
500 16 : printf("\n--- Compose/send: help pages ---\n");
501 16 : RUN_TEST(test_help_compose);
502 15 : RUN_TEST(test_help_send);
503 14 : RUN_TEST(test_help_reply);
504 :
505 13 : printf("\n--- Compose/send: batch send ---\n");
506 13 : RUN_TEST(test_send_missing_to);
507 12 : RUN_TEST(test_send_batch_ok);
508 11 : RUN_TEST(test_send_no_smtp_config);
509 :
510 10 : printf("\n--- Compose/send: interactive compose ---\n");
511 10 : RUN_TEST(test_compose_abort_no_to);
512 9 : RUN_TEST(test_compose_editor_send);
513 8 : RUN_TEST(test_compose_confirm_cancel);
514 7 : RUN_TEST(test_compose_sent_folder);
515 6 : RUN_TEST(test_reply_missing_uid);
516 5 : RUN_TEST(test_reply_no_cr_in_quote);
517 :
518 4 : printf("\n--- Config command (US-24) ---\n");
519 4 : RUN_TEST(test_config_show);
520 3 : RUN_TEST(test_config_help);
521 2 : RUN_TEST(test_config_imap_help);
522 :
523 1 : stop_smtp_server();
524 :
525 1 : printf("\n--- PTY Compose Test Results ---\n");
526 1 : printf("Tests Run: %d\n", g_tests_run);
527 1 : printf("Tests Passed: %d\n", g_tests_run - g_tests_failed);
528 1 : printf("Tests Failed: %d\n", g_tests_failed);
529 :
530 1 : return g_tests_failed > 0 ? 1 : 0;
531 : }
|