CSE 120: Testing System Calls

No one likes testing, yet it is an important skill to learn. Fortunately, the Nachos projects provide ample opportunity to hone your testing skillz. This page provides some suggestions for how to test and some example test programs focused on the write system call. Of course, you should implement your own additional tests for write as well as extensive tests for the other system calls you implement in project 2.

My suggestion is to approach writing test cases in three stages. In the first stage write short, simple tests (unit tests) that each aim to test one specific aspect of functionality. For example, the following test program validates that writing to the standard output stream one byte at a time works as expected. Moreover, it does not need any of the other system calls to be implemented yet:

And this program tests that both read (from standard input) and write (to standard output) work together:

An important aspect of unit tests is that, when something goes wrong, you know where to look for the problem in your code — precisely because you are testing some specific functionality.

Once you have tested basic functionality, then move on to more elaborate tests that (1) use a combination of system calls and functionality, and (2) test for a variety of corner cases. To give you an example of what such a test might look like, see the following program to test write:

DO NOT use this program to test your initial implementation. When something goes wrong, you will not know where to start looking to debug your Nachos code. Only use this program once you have basic functionality both working and tested.

For the third category, use a program that puts stress on the system. For example, you can use the following program to stress writing to standard output:

Also, rather than re-using the same file to write tests — writing test code, checking that it works, then deleting and writing new test code in the same file — write the tests in multiple files. When you make a change to your Nachos implementation, you will then be able to run through your old tests to confirm that they still work.

Testing: The syscall.h file and the project page specify the requirements for the various system calls. For convenience, here we summarize the functionality that test programs should exercise. Operating systems need to be very defensive when handling system calls, so testing includes exercising error situations in addition to correct situations.

General: system calls should return an error instead of throwing an exception; file name arguments longer than 256 bytes (not including the null byte) return an error.

open: opens an existing file and returns a file descriptor for it, and an error otherwise; opening the same file multiple times returns different file descriptors for each open; opening files does not interfere with stdin and stdout; each process can use 16 file descriptors.

creat: similar to open, but creates a new file if it does not exist; opens a file if it already exists; returns an error if the file cannot be created or opened.

read: works on both stdin and on files; checks arguments for correctness (valid file descriptor, file buffer pointer, count); the data read is actually the data stored in the file; the number of bytes read may be less than count.

write: works on both stdout and on files; checks arguments for correctness (valid file descriptor, file buffer pointer, count); the data written is actually stored in the file; the number of bytes written matches count.

close: file must be opened; uses the file system to close the file; frees up the file descriptor so that it can be used again; reading or writing to a file descriptor that was closed returns an error.

unlink: returns success only if the file system successfully removes the file, and an error otherwise; validates the file name argument.