From f8e5453f2b1dfd29596c378cb2da7740f3bf1b21 Mon Sep 17 00:00:00 2001 From: Ingo Weinhold Date: Sun, 20 Mar 2005 22:35:05 +0000 Subject: [PATCH] Added a small program for testing unblocking on close and select support. Not quite exhaustive and tests for ECHO mode are missing, since I couldn't figure out how to turn it on. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@11932 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- src/tests/add-ons/kernel/Jamfile | 1 + src/tests/add-ons/kernel/drivers/Jamfile | 3 + src/tests/add-ons/kernel/drivers/tty/Jamfile | 6 + .../add-ons/kernel/drivers/tty/tty-test.cpp | 847 ++++++++++++++++++ 4 files changed, 857 insertions(+) create mode 100644 src/tests/add-ons/kernel/drivers/Jamfile create mode 100644 src/tests/add-ons/kernel/drivers/tty/Jamfile create mode 100644 src/tests/add-ons/kernel/drivers/tty/tty-test.cpp diff --git a/src/tests/add-ons/kernel/Jamfile b/src/tests/add-ons/kernel/Jamfile index 1e5d92cfe3..3bb6a5173e 100644 --- a/src/tests/add-ons/kernel/Jamfile +++ b/src/tests/add-ons/kernel/Jamfile @@ -29,5 +29,6 @@ SEARCH on [ FGristFiles ] = [ FDirName $(OBOS_TOP) src kernel core util ] ; # SubInclude OBOS_TOP src tests add-ons kernel disk_scanner ; +SubInclude OBOS_TOP src tests add-ons kernel drivers ; SubInclude OBOS_TOP src tests add-ons kernel file_systems ; SubInclude OBOS_TOP src tests add-ons kernel network ; diff --git a/src/tests/add-ons/kernel/drivers/Jamfile b/src/tests/add-ons/kernel/drivers/Jamfile new file mode 100644 index 0000000000..d4687a3a5a --- /dev/null +++ b/src/tests/add-ons/kernel/drivers/Jamfile @@ -0,0 +1,3 @@ +SubDir OBOS_TOP src tests add-ons kernel drivers ; + +SubInclude OBOS_TOP src tests add-ons kernel drivers tty ; diff --git a/src/tests/add-ons/kernel/drivers/tty/Jamfile b/src/tests/add-ons/kernel/drivers/tty/Jamfile new file mode 100644 index 0000000000..367f7ff75c --- /dev/null +++ b/src/tests/add-ons/kernel/drivers/tty/Jamfile @@ -0,0 +1,6 @@ +SubDir OBOS_TOP src tests add-ons kernel drivers tty ; + +UsePrivateHeaders [ FDirName kernel ] ; + +SimpleTest tty-test : tty-test.cpp : libroot.so ; + diff --git a/src/tests/add-ons/kernel/drivers/tty/tty-test.cpp b/src/tests/add-ons/kernel/drivers/tty/tty-test.cpp new file mode 100644 index 0000000000..8ebd5d46ef --- /dev/null +++ b/src/tests/add-ons/kernel/drivers/tty/tty-test.cpp @@ -0,0 +1,847 @@ +/* + * Copyright 2005, Ingo Weinhold, bonefish@users.sf.net. + * Distributed under the terms of the MIT License. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +// Test cases: +// +// 1. unblock on close: +// +// * unblock, when the same cookie is closed +// - read +// - write (with, without ECHO) +// * unblock write operations, when the other tty is closed +// * unblock slave operations, when the master is closed +// +// +// 2. select: +// +// * notify read, write, if ready when select()ing +// * notify read, write, error on close of the other TTY +// (the select() behavior when closing the same TTY is undefined) +// * notify read after pending write +// - with ECHO +// - without ECHO +// * notify write after pending read and full buffer +// - with ECHO +// - without ECHO +// * don't notify when there was a pending read/write +// +// +// TODO: There are no ECHO tests yet, since I don't know how to turn it on. + +#define CHK(condition) assert(condition) +#define RUN_TEST(test) (test)->Run() +#define CHK_ALIVE(thread) CHK((thread)->IsAlive()) +#define CHK_DEAD(thread) CHK(!(thread)->IsAlive()) + +// FDSet +struct FDSet : fd_set { + FDSet() + { + Clear(); + } + + void Clear() + { + FD_ZERO(this); + fCount = 0; + } + + void Add(int fd) + { + if (fd < 0 || fd >= FD_SETSIZE) { + fprintf(stderr, "FDSet::Add(%d): Invalid FD.\n", fd); + return; + } + + FD_SET(fd, this); + if (fd >= fCount) + fCount = fd + 1; + } + + int Count() const + { + return fCount; + } + + void UpdateCount() + { + if (fCount > 0) { + for (int i = fCount - 1; i >= 0; i--) { + if (FD_ISSET(i, this)) { + fCount = i + 1; + return; + } + } + fCount = 0; + } + } + + bool operator==(const FDSet &other) const + { + if (fCount != other.fCount) + return false; + + for (int i = 0; i < fCount; i++) { + if (FD_ISSET(i, this) != FD_ISSET(i, &other)) + return false; + } + + return true; + } + + bool operator!=(const FDSet &other) const + { + return !(*this == other); + } + +private: + int fCount; +}; + +// SelectSet +class SelectSet { +public: + SelectSet() {} + ~SelectSet() {} + + void Clear() + { + fReadSet.Clear(); + fWriteSet.Clear(); + fErrorSet.Clear(); + } + + void AddReadFD(int fd) + { + fReadSet.Add(fd); + } + + void AddWriteFD(int fd) + { + fWriteSet.Add(fd); + } + + void AddErrorFD(int fd) + { + fErrorSet.Add(fd); + } + + int Select(bigtime_t timeout = -1) + { + int count = max(max(fReadSet.Count(), fWriteSet.Count()), + fErrorSet.Count()); + fd_set *readSet = (fReadSet.Count() > 0 ? &fReadSet : NULL); + fd_set *writeSet = (fWriteSet.Count() > 0 ? &fWriteSet : NULL); + fd_set *errorSet = (fErrorSet.Count() > 0 ? &fErrorSet : NULL); + + timeval tv = { timeout / 1000000, timeout % 1000000 }; + + int result = select(count, readSet, writeSet, errorSet, + (timeout >= 0 ? &tv : NULL)); + if (result <= 0) { + Clear(); + } else { + fReadSet.UpdateCount(); + fWriteSet.UpdateCount(); + fErrorSet.UpdateCount(); + } + + return result; + } + + bool operator==(const SelectSet &other) const + { + return (fReadSet == other.fReadSet + && fWriteSet == other.fWriteSet + && fErrorSet == other.fErrorSet); + } + + bool operator!=(const SelectSet &other) const + { + return !(*this == other); + } + +private: + FDSet fReadSet; + FDSet fWriteSet; + FDSet fErrorSet; +}; + +// Runnable +class Runnable { +public: + virtual ~Runnable() {} + virtual int32 Run() = 0; +}; + +// Caller +template +class Caller : public Runnable { +public: + Caller(Type *object, int32 (Type::*function)()) + : fObject(object), + fFunction(function) + { + } + + virtual int32 Run() + { + return (fObject->*fFunction)(); + } + +private: + Type *fObject; + int32 (Type::*fFunction)(); +}; + +// create_caller +template +static inline Runnable * +create_caller(Type *object, int32 (Type::*function)()) +{ + return new Caller(object, function); +} + +#define CALLER(object, function) create_caller(object, function) + +// Thread +struct Thread : public DoublyLinkedListLinkImpl { +public: + Thread(Runnable *runnable, const char *name) + : fRunnable(runnable) + { + fThread = spawn_thread(_Entry, name, B_NORMAL_PRIORITY, this); + if (fThread < 0) { + sprintf("Failed to spawn thread: %s\n", strerror(fThread)); + exit(1); + } + } + + ~Thread() + { + Kill(); + delete fRunnable; + } + + void Resume() + { + resume_thread(fThread); + } + + void WaitFor() + { + status_t result; + wait_for_thread(fThread, &result); + } + + void Kill() + { + kill_thread(fThread); + } + + bool IsAlive() + { + thread_info info; + return (get_thread_info(fThread, &info) == B_OK); + } + +private: + static int32 _Entry(void *data) + { + return ((Thread*)data)->fRunnable->Run(); + } + + thread_id fThread; + Runnable *fRunnable; +}; + +// TestCase +class TestCase { +public: + TestCase() {} + virtual ~TestCase() + { + while (Thread *thread = fThreads.Head()) { + fThreads.Remove(thread); + delete thread; + } + } + + void Run() + { + Test(); + delete this; + } + +protected: + virtual void Test() = 0; + + Thread *CreateThread(Runnable *runnable, const char *name) + { + Thread *thread = new Thread(runnable, name); + fThreads.Add(thread); + return thread; + } + + static void WriteUntilBlock(int fd) + { + // set non-blocking I/O + if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) { + fprintf(stderr, "WriteUntilBlock(): Failed to set non-blocking I/O " + "mode: %s\n", strerror(errno)); + exit(1); + } + + // write till blocking + char buffer[1024]; + memset(buffer, 'A', sizeof(buffer)); + ssize_t bytesWritten; + do { + bytesWritten = write(fd, buffer, sizeof(buffer)); + } while (bytesWritten > 0 || errno == B_INTERRUPTED); + + if (bytesWritten < 0 && errno != B_WOULD_BLOCK) { + fprintf(stderr, "WriteUntilBlock(): Writing failed: %s\n", + strerror(errno)); + exit(1); + } + + // reset to blocking I/O + if (fcntl(fd, F_SETFL, 0) < 0) { + fprintf(stderr, "WriteUntilBlock(): Failed to set blocking I/O " + "mode: %s\n", strerror(errno)); + exit(1); + } + } + + static void ReadDontFail(int fd, int32 size) + { + char buffer[1024]; + + while (size > 0) { + ssize_t bytesRead; + do { + int32 toRead = sizeof(buffer); + if (toRead > size) + toRead = size; + + bytesRead = read(fd, buffer, toRead); + } while (bytesRead < 0 && errno == B_INTERRUPTED); + + if (bytesRead < 0) { + fprintf(stderr, "ReadDontFail(): Failed to read: %s\n", + strerror(errno)); + exit(1); + } + + size -= bytesRead; + } + } + + static void WriteDontFail(int fd, int32 size) + { + char buffer[1024]; + memset(buffer, 'A', sizeof(buffer)); + + while (size > 0) { + ssize_t bytesWritten; + do { + int32 toWrite = sizeof(buffer); + if (toWrite > size) + toWrite = size; + + bytesWritten = write(fd, buffer, toWrite); + } while (bytesWritten < 0 && errno == B_INTERRUPTED); + + if (bytesWritten < 0) { + fprintf(stderr, "WriteDontFail(): Failed to write: %s\n", + strerror(errno)); + exit(1); + } + + size -= bytesWritten; + } + } + + void SetEcho(int fd) + { + // TODO: How to set echo mode? + } + +private: + typedef DoublyLinkedList ThreadList; + + ThreadList fThreads; +}; + + +// open_tty +static int +open_tty(int index, bool master) +{ + if (index < 0 || index >= 16) + fprintf(stderr, "open_tty(%d, %d): Bad index!\n", index, master); + + char path[32]; + sprintf(path, "/dev/%ct/r%x", (master ? 'p' : 't'), index); + int fd = open(path, O_RDWR); + + if (fd < 0) { + fprintf(stderr, "Failed to open tty `%s': %s\n", path, strerror(errno)); + exit(1); + } + + return fd; +} + + +static void +close_tty(int &fd) +{ + if (fd >= 0) { + close(fd); + fd = -1; + } +} + + +// #pragma mark - + +// TestUnblockOnCloseRead +class TestUnblockOnCloseRead : public TestCase { +public: + TestUnblockOnCloseRead(bool master, bool crossOver) + : fMaster(-1), + fSlave(-1), + fTestMaster(master), + fCrossOver(crossOver) + { + printf("TestUnblockOnCloseRead(%d, %d)\n", master, crossOver); + } + + +protected: + virtual ~TestUnblockOnCloseRead() + { + close_tty(fMaster); + close_tty(fSlave); + } + + virtual void Test() + { + fMaster = open_tty(0, true); + fSlave = open_tty(0, false); + + Thread *thread = CreateThread( + CALLER(this, &TestUnblockOnCloseRead::_Reader), "reader"); + thread->Resume(); + + snooze(100000); + CHK_ALIVE(thread); + + if (fCrossOver) + close_tty(fTestMaster ? fSlave : fMaster); + else + close_tty(fTestMaster ? fMaster : fSlave); + + snooze(100000); + CHK_DEAD(thread); + } + +private: + int32 _Reader() + { + char buffer[32]; + ssize_t bytesRead = read((fTestMaster ? fMaster : fSlave), buffer, + sizeof(buffer)); + + CHK((bytesRead < 0)); + + return 0; + } + +private: + int fMaster; + int fSlave; + bool fTestMaster; + bool fCrossOver; +}; + +// TestUnblockOnCloseWrite +class TestUnblockOnCloseWrite : public TestCase { +public: + TestUnblockOnCloseWrite(bool master, bool crossOver, bool echo) + : fMaster(-1), + fSlave(-1), + fTestMaster(master), + fCrossOver(crossOver), + fEcho(echo) + { + printf("TestUnblockOnCloseWrite(%d, %d, %d)\n", master, crossOver, + echo); + } + + +protected: + virtual ~TestUnblockOnCloseWrite() + { + close_tty(fMaster); + close_tty(fSlave); + } + + virtual void Test() + { + fMaster = open_tty(0, true); + fSlave = open_tty(0, false); + + if (fEcho) + SetEcho((fTestMaster ? fSlave : fMaster)); + + WriteUntilBlock((fTestMaster ? fMaster : fSlave)); + + Thread *thread = CreateThread( + CALLER(this, &TestUnblockOnCloseWrite::_Writer), "writer"); + thread->Resume(); + + snooze(100000); + CHK_ALIVE(thread); + + if (fCrossOver) + close_tty(fTestMaster ? fSlave : fMaster); + else + close_tty(fTestMaster ? fMaster : fSlave); + + snooze(100000); + CHK_DEAD(thread); + } + +private: + int32 _Writer() + { + char buffer[32]; + memset(buffer, 'A', sizeof(buffer)); + ssize_t bytesWritten = write((fTestMaster ? fMaster : fSlave), buffer, + sizeof(buffer)); + + CHK((bytesWritten < 0)); + + return 0; + } + +private: + int fMaster; + int fSlave; + bool fTestMaster; + bool fCrossOver; + bool fEcho; +}; + +// TestSelectAlreadyReady +class TestSelectAlreadyReady : public TestCase { +public: + TestSelectAlreadyReady(bool master, bool write) + : fMaster(-1), + fSlave(-1), + fTestMaster(master), + fWrite(write) + { + printf("TestSelectAlreadyReady(%d, %d)\n", master, write); + } + + +protected: + virtual ~TestSelectAlreadyReady() + { + close_tty(fMaster); + close_tty(fSlave); + } + + virtual void Test() + { + fMaster = open_tty(0, true); + fSlave = open_tty(0, false); + + if (!fWrite) + WriteDontFail((fTestMaster ? fSlave : fMaster), 1); + + Thread *thread = CreateThread( + CALLER(this, &TestSelectAlreadyReady::_Selector), "selector"); + thread->Resume(); + + snooze(100000); + CHK_DEAD(thread); + } + +private: + int32 _Selector() + { + SelectSet selectSet; + SelectSet compareSet; + if (fWrite) { + selectSet.AddWriteFD((fTestMaster ? fMaster : fSlave)); + compareSet.AddWriteFD((fTestMaster ? fMaster : fSlave)); + } else { + selectSet.AddReadFD((fTestMaster ? fMaster : fSlave)); + compareSet.AddReadFD((fTestMaster ? fMaster : fSlave)); + } + + int result = selectSet.Select(); + CHK(result > 0); + CHK(selectSet == compareSet); + + return 0; + } + +private: + int fMaster; + int fSlave; + bool fTestMaster; + bool fWrite; +}; + +// TestSelectNotifyOnClose +class TestSelectNotifyOnClose : public TestCase { +public: + TestSelectNotifyOnClose(bool master) + : fMaster(-1), + fSlave(-1), + fTestMaster(master) + { + printf("TestSelectNotifyOnClose(%d)\n", master); + } + + +protected: + virtual ~TestSelectNotifyOnClose() + { + close_tty(fMaster); + close_tty(fSlave); + } + + virtual void Test() + { + fMaster = open_tty(0, true); + fSlave = open_tty(0, false); + + WriteUntilBlock((fTestMaster ? fMaster : fSlave)); + + Thread *thread = CreateThread( + CALLER(this, &TestSelectNotifyOnClose::_Selector), "selector"); + thread->Resume(); + + snooze(100000); + CHK_ALIVE(thread); + + close_tty((fTestMaster ? fSlave : fMaster)); + + snooze(100000); + CHK_DEAD(thread); + } + +private: + int32 _Selector() + { + int fd = (fTestMaster ? fMaster : fSlave); + + SelectSet selectSet; + selectSet.AddReadFD(fd); + selectSet.AddWriteFD(fd); + selectSet.AddErrorFD(fd); + + // In case the slave is closed while we select() on the master, only a + // `write' event will arrive. + SelectSet compareSet; + if (!fTestMaster) { + compareSet.AddReadFD(fd); + compareSet.AddErrorFD(fd); + } + compareSet.AddWriteFD(fd); + + int result = selectSet.Select(); + CHK(result > 0); + CHK(selectSet == compareSet); + + return 0; + } + +private: + int fMaster; + int fSlave; + bool fTestMaster; +}; + +// TestSelectNotifyAfterPending +class TestSelectNotifyAfterPending : public TestCase { +public: + TestSelectNotifyAfterPending(bool master, bool write, bool unblock) + : fMaster(-1), + fSlave(-1), + fTestMaster(master), + fWrite(write), + fUnblock(unblock) + { + printf("TestSelectNotifyAfterPending(%d, %d, %d)\n", master, write, + unblock); + } + + +protected: + virtual ~TestSelectNotifyAfterPending() + { + close_tty(fMaster); + close_tty(fSlave); + } + + virtual void Test() + { + fMaster = open_tty(0, true); + fSlave = open_tty(0, false); + + if (fWrite) + WriteUntilBlock((fTestMaster ? fMaster : fSlave)); + + Thread *readWriter = CreateThread( + CALLER(this, &TestSelectNotifyAfterPending::_ReadWriter), + "read-writer"); + Thread *selector = CreateThread( + CALLER(this, &TestSelectNotifyAfterPending::_Selector), "selector"); + + readWriter->Resume(); + selector->Resume(); + + snooze(100000); + CHK_ALIVE(readWriter); + CHK_ALIVE(selector); + + if (fUnblock) { + // unblock the read-writer and the selector + if (fWrite) + ReadDontFail((fTestMaster ? fSlave : fMaster), 2); + else + WriteDontFail((fTestMaster ? fSlave : fMaster), 2); + + snooze(100000); + CHK_DEAD(readWriter); + CHK_DEAD(selector); + + } else { + // unblock the read-writer, but not the selector + if (fWrite) + ReadDontFail((fTestMaster ? fSlave : fMaster), 1); + else + WriteDontFail((fTestMaster ? fSlave : fMaster), 1); + + snooze(100000); + CHK_DEAD(readWriter); + CHK_ALIVE(selector); + + close_tty((fTestMaster ? fMaster : fSlave)); + + snooze(100000); + CHK_DEAD(selector); + } + } + +private: + int32 _ReadWriter() + { + int fd = (fTestMaster ? fMaster : fSlave); + if (fWrite) + WriteDontFail(fd, 1); + else + ReadDontFail(fd, 1); + + return 0; + } + + int32 _Selector() + { + int fd = (fTestMaster ? fMaster : fSlave); + + SelectSet selectSet; + SelectSet compareSet; + + if (fWrite) { + selectSet.AddWriteFD(fd); + compareSet.AddWriteFD(fd); + } else { + selectSet.AddReadFD(fd); + compareSet.AddReadFD(fd); + } + + int result = selectSet.Select(); + CHK(result > 0); + CHK(selectSet == compareSet); + + return 0; + } + +private: + int fMaster; + int fSlave; + bool fTestMaster; + bool fWrite; + bool fUnblock; +}; + + + +// #pragma mark - + +int +main() +{ + // unblock tests + + RUN_TEST(new TestUnblockOnCloseRead(true, false)); + RUN_TEST(new TestUnblockOnCloseRead(false, false)); + RUN_TEST(new TestUnblockOnCloseRead(false, true)); + + RUN_TEST(new TestUnblockOnCloseWrite(true, false, false)); + RUN_TEST(new TestUnblockOnCloseWrite(false, false, false)); + RUN_TEST(new TestUnblockOnCloseWrite(true, true, false)); + RUN_TEST(new TestUnblockOnCloseWrite(false, true, false)); +// TODO: How to enable echo mode? +// RUN_TEST(new TestUnblockOnCloseWrite(true, false, true)); +// RUN_TEST(new TestUnblockOnCloseWrite(false, false, true)); +// RUN_TEST(new TestUnblockOnCloseWrite(true, true, true)); +// RUN_TEST(new TestUnblockOnCloseWrite(false, true, true)); + + // select tests + + RUN_TEST(new TestSelectAlreadyReady(true, true)); + RUN_TEST(new TestSelectAlreadyReady(false, true)); + RUN_TEST(new TestSelectAlreadyReady(true, false)); + RUN_TEST(new TestSelectAlreadyReady(false, false)); + + RUN_TEST(new TestSelectNotifyOnClose(true)); + RUN_TEST(new TestSelectNotifyOnClose(false)); + + RUN_TEST(new TestSelectNotifyAfterPending(false, false, false)); + RUN_TEST(new TestSelectNotifyAfterPending(false, false, true)); + RUN_TEST(new TestSelectNotifyAfterPending(false, true, false)); + RUN_TEST(new TestSelectNotifyAfterPending(false, true, true)); + RUN_TEST(new TestSelectNotifyAfterPending(true, false, false)); + RUN_TEST(new TestSelectNotifyAfterPending(true, false, true)); + RUN_TEST(new TestSelectNotifyAfterPending(true, true, false)); + RUN_TEST(new TestSelectNotifyAfterPending(true, true, true)); + + return 0; +}