Add job control regression tests

This commit is contained in:
Ruihan Li
2025-05-08 15:17:53 +08:00
committed by Jianfeng Jiang
parent ff907d1131
commit da82ca619f
2 changed files with 236 additions and 0 deletions

View File

@ -0,0 +1,235 @@
// SPDX-License-Identifier: MPL-2.0
#include "../network/test.h"
#include <unistd.h>
#include <pty.h>
#include <sys/wait.h>
static pid_t sid, child;
static int master, slave;
FN_SETUP(openpty)
{
CHECK(openpty(&master, &slave, NULL, NULL, NULL));
}
END_SETUP()
FN_SETUP(run_in_new_session)
{
int status;
if (CHECK(fork()) != 0) {
CHECK_WITH(wait(&status),
WIFEXITED(status) && WEXITSTATUS(status) == 0);
exit(EXIT_SUCCESS);
}
sid = CHECK(setsid());
if ((child = CHECK(fork())) == 0) {
child = getpid();
// TODO: Linux allows to specify PIDs (instead of PGIDs) as
// parameters to `TIOCSPGRP`. We may want to support this as
// well. For more details, see:
// <https://elixir.bootlin.com/linux/v6.14.5/source/drivers/tty/tty_jobctrl.c#L434-L453>.
CHECK(setpgrp());
}
signal(SIGHUP, SIG_IGN);
signal(SIGTTIN, SIG_IGN);
// TODO: We should forbid some TTY operations (e.g., `TIOCSPGRP`) if
// the `SIGTTOU` signal is not blocked or ignored and the current
// process is not in the foreground process group. However, this is
// not currently implemented in Asterinas yet.
signal(SIGTTOU, SIG_IGN);
}
END_SETUP()
// From now on, we'll run all the tests twice, once as a session leader
// and once not as a session leader. Most tests will only work in one of
// the two cases, so check at the beginning and skip the test if it's
// not the expected case.
static int is_leader(void)
{
return getpid() == sid;
}
// TODO: Find a better way to synchronize between the two processes.
#define __NAMED_BARRIER(name) \
FN_SETUP(name) \
{ \
CHECK(usleep(100 * 1000)); \
} \
END_SETUP()
#define NAMED_BARRIER(name) __NAMED_BARRIER(__CONCAT(barrier, name))
#define BARRIER(name) NAMED_BARRIER(__LINE__)
FN_TEST(not_our_tty)
{
pid_t arg;
// Operate on the controlling session
TEST_ERRNO(ioctl(master, TIOCNOTTY), ENOTTY);
TEST_ERRNO(ioctl(slave, TIOCNOTTY), ENOTTY);
TEST_ERRNO(ioctl(STDIN_FILENO, TIOCNOTTY), ENOTTY);
TEST_ERRNO(ioctl(master, TIOCGSID, &arg), ENOTTY);
TEST_ERRNO(ioctl(slave, TIOCGSID, &arg), ENOTTY);
TEST_ERRNO(ioctl(STDIN_FILENO, TIOCGSID, &arg), ENOTTY);
// Operate on the foreground process group
arg = 0xdeadbeef;
TEST_RES(ioctl(master, TIOCGPGRP, &arg), arg == 0);
TEST_ERRNO(ioctl(slave, TIOCGPGRP, &arg), ENOTTY);
TEST_ERRNO(ioctl(STDIN_FILENO, TIOCGPGRP, &arg), ENOTTY);
TEST_ERRNO(ioctl(master, TIOCSPGRP, &arg), ENOTTY);
TEST_ERRNO(ioctl(slave, TIOCSPGRP, &arg), ENOTTY);
TEST_ERRNO(ioctl(STDIN_FILENO, TIOCSPGRP, &arg), ENOTTY);
}
END_TEST()
FN_TEST(nonleader_set_tty)
{
if (is_leader())
return;
// Only session leaders can use `TIOCSCTTY`, but we're not the leader
TEST_ERRNO(ioctl(master, TIOCSCTTY, 0), EPERM);
TEST_ERRNO(ioctl(slave, TIOCSCTTY, 0), EPERM);
TEST_ERRNO(ioctl(STDIN_FILENO, TIOCSCTTY, 0), EPERM);
}
END_TEST()
BARRIER()
FN_TEST(leader_set_unset_tty)
{
pid_t arg;
if (!is_leader())
return;
// We can use `TIOCSCTTY` on the master PTY
TEST_SUCC(ioctl(master, TIOCSCTTY, 0));
arg = 0xdeadbeef;
TEST_RES(ioctl(master, TIOCGSID, &arg), arg == sid);
arg = 0xdeadbeef;
TEST_RES(ioctl(slave, TIOCGSID, &arg), arg == sid);
// `TIOCSCTTY` on the same TTY will succeed
TEST_SUCC(ioctl(master, TIOCSCTTY, 0));
TEST_SUCC(ioctl(slave, TIOCSCTTY, 0));
// `TIOCSCTTY` on a different TTY will fail
TEST_ERRNO(ioctl(STDIN_FILENO, TIOCSCTTY, 0), EPERM);
// `TIOCNOTTY` to clear the associated TTY
TEST_ERRNO(ioctl(master, TIOCNOTTY), ENOTTY);
TEST_SUCC(ioctl(slave, TIOCNOTTY));
TEST_ERRNO(ioctl(master, TIOCGSID, &arg), ENOTTY);
TEST_ERRNO(ioctl(slave, TIOCGSID, &arg), ENOTTY);
// We can also use `TIOCSCTTY` on the slave PTY
TEST_SUCC(ioctl(slave, TIOCSCTTY, 0));
arg = 0xdeadbeef;
TEST_RES(ioctl(master, TIOCGSID, &arg), arg == sid);
arg = 0xdeadbeef;
TEST_RES(ioctl(slave, TIOCGSID, &arg), arg == sid);
}
END_TEST()
BARRIER()
FN_TEST(nonleader_unset_tty)
{
if (is_leader())
return;
// Only session leaders can use `TIOCNOTTY`, but we're not the leader
TEST_ERRNO(ioctl(master, TIOCNOTTY), ENOTTY);
TEST_ERRNO(ioctl(slave, TIOCNOTTY), ENOTTY);
TEST_ERRNO(ioctl(STDIN_FILENO, TIOCNOTTY), ENOTTY);
}
END_TEST()
FN_TEST(query_foreground)
{
pid_t arg;
// All processes can use `TIOCGPGRP` on the master PTY
TEST_RES(ioctl(master, TIOCGPGRP, &arg), arg == sid);
// Only session leaders can use `TIOCGPGRP` on the slave PTY
if (is_leader())
TEST_RES(ioctl(slave, TIOCGPGRP, &arg), arg == sid);
else
TEST_ERRNO(ioctl(slave, TIOCGPGRP, &arg), ENOTTY);
}
END_TEST()
BARRIER()
FN_TEST(leader_set_foreground)
{
pid_t arg;
if (!is_leader())
return;
// Only session leaders can use `TIOCSPGRP`, and we're the leader
arg = child;
TEST_SUCC(ioctl(master, TIOCSPGRP, &arg));
arg = 0xdeadbeef;
TEST_RES(ioctl(slave, TIOCGPGRP, &arg), arg == child);
arg = sid;
TEST_SUCC(ioctl(master, TIOCSPGRP, &arg));
arg = 0xdeadbeef;
TEST_RES(ioctl(slave, TIOCGPGRP, &arg), arg == sid);
arg = child;
TEST_SUCC(ioctl(slave, TIOCSPGRP, &arg));
arg = 0xdeadbeef;
TEST_RES(ioctl(master, TIOCGPGRP, &arg), arg == child);
arg = sid;
TEST_SUCC(ioctl(slave, TIOCSPGRP, &arg));
arg = 0xdeadbeef;
TEST_RES(ioctl(master, TIOCGPGRP, &arg), arg == sid);
}
END_TEST()
BARRIER()
FN_TEST(nonleader_set_foreground)
{
pid_t arg = child;
if (is_leader())
return;
// Only session leaders can use `TIOCSPGRP`, but we're not the leader
TEST_ERRNO(ioctl(master, TIOCSPGRP, &arg), ENOTTY);
TEST_ERRNO(ioctl(slave, TIOCSPGRP, &arg), ENOTTY);
}
END_TEST()
FN_SETUP(cleanup)
{
int status;
if (!is_leader())
return;
CHECK_WITH(wait(&status),
WIFEXITED(status) && WEXITSTATUS(status) == 0);
}
END_SETUP()

View File

@ -30,6 +30,7 @@ mmap/mmap_and_fork
mmap/mmap_shared_filebacked
mmap/mmap_readahead
process/group_session
process/job_control
pthread/pthread_test
pty/open_pty
sched/sched_attr