aboutsummaryrefslogtreecommitdiffstats
path: root/src/tests/userprog
diff options
context:
space:
mode:
Diffstat (limited to 'src/tests/userprog')
-rw-r--r--src/tests/userprog/Grading11
-rw-r--r--src/tests/userprog/Make.tests125
-rw-r--r--src/tests/userprog/Make.tests.odig132
-rw-r--r--src/tests/userprog/Rubric.functionality52
-rw-r--r--src/tests/userprog/Rubric.robustness48
-rw-r--r--src/tests/userprog/args-dbl-space.ck15
-rw-r--r--src/tests/userprog/args-many.ck35
-rw-r--r--src/tests/userprog/args-multiple.ck17
-rw-r--r--src/tests/userprog/args-none.ck13
-rw-r--r--src/tests/userprog/args-single.ck14
-rw-r--r--src/tests/userprog/args.c25
-rw-r--r--src/tests/userprog/bad-jump.c13
-rw-r--r--src/tests/userprog/bad-jump.ck9
-rw-r--r--src/tests/userprog/bad-jump2.c13
-rw-r--r--src/tests/userprog/bad-jump2.ck9
-rw-r--r--src/tests/userprog/bad-read.c13
-rw-r--r--src/tests/userprog/bad-read.ck9
-rw-r--r--src/tests/userprog/bad-read2.c13
-rw-r--r--src/tests/userprog/bad-read2.ck9
-rw-r--r--src/tests/userprog/bad-write.c12
-rw-r--r--src/tests/userprog/bad-write.ck9
-rw-r--r--src/tests/userprog/bad-write2.c12
-rw-r--r--src/tests/userprog/bad-write2.ck9
-rw-r--r--src/tests/userprog/boundary.c33
-rw-r--r--src/tests/userprog/boundary.h7
-rw-r--r--src/tests/userprog/child-bad.c14
-rw-r--r--src/tests/userprog/child-close.c28
-rw-r--r--src/tests/userprog/child-rox.c55
-rw-r--r--src/tests/userprog/child-simple.c15
-rw-r--r--src/tests/userprog/close-bad-fd.c11
-rw-r--r--src/tests/userprog/close-bad-fd.ck13
-rw-r--r--src/tests/userprog/close-normal.c14
-rw-r--r--src/tests/userprog/close-normal.ck12
-rw-r--r--src/tests/userprog/close-stdin.c11
-rw-r--r--src/tests/userprog/close-stdin.ck13
-rw-r--r--src/tests/userprog/close-stdout.c11
-rw-r--r--src/tests/userprog/close-stdout.ck13
-rw-r--r--src/tests/userprog/close-twice.c18
-rw-r--r--src/tests/userprog/close-twice.ck19
-rw-r--r--src/tests/userprog/create-bad-ptr.c12
-rw-r--r--src/tests/userprog/create-bad-ptr.ck9
-rw-r--r--src/tests/userprog/create-bound.c14
-rw-r--r--src/tests/userprog/create-bound.ck11
-rw-r--r--src/tests/userprog/create-empty.c10
-rw-r--r--src/tests/userprog/create-empty.ck14
-rw-r--r--src/tests/userprog/create-exists.c16
-rw-r--r--src/tests/userprog/create-exists.ck15
-rw-r--r--src/tests/userprog/create-long.c17
-rw-r--r--src/tests/userprog/create-long.ck11
-rw-r--r--src/tests/userprog/create-normal.c10
-rw-r--r--src/tests/userprog/create-normal.ck11
-rw-r--r--src/tests/userprog/create-null.c11
-rw-r--r--src/tests/userprog/create-null.ck9
-rw-r--r--src/tests/userprog/exec-arg.c10
-rw-r--r--src/tests/userprog/exec-arg.ck17
-rw-r--r--src/tests/userprog/exec-bad-ptr.c11
-rw-r--r--src/tests/userprog/exec-bad-ptr.ck13
-rw-r--r--src/tests/userprog/exec-missing.c12
-rw-r--r--src/tests/userprog/exec-missing.ck31
-rw-r--r--src/tests/userprog/exec-multiple.c14
-rw-r--r--src/tests/userprog/exec-multiple.ck18
-rw-r--r--src/tests/userprog/exec-once.c11
-rw-r--r--src/tests/userprog/exec-once.ck12
-rw-r--r--src/tests/userprog/exit.c11
-rw-r--r--src/tests/userprog/exit.ck9
-rw-r--r--src/tests/userprog/halt.c11
-rw-r--r--src/tests/userprog/halt.ck15
-rw-r--r--src/tests/userprog/lib/.cvsignore1
-rw-r--r--src/tests/userprog/lib/user/.cvsignore1
-rw-r--r--src/tests/userprog/lib/user/.dummy0
-rw-r--r--src/tests/userprog/multi-child-fd.c25
-rw-r--r--src/tests/userprog/multi-child-fd.ck25
-rw-r--r--src/tests/userprog/multi-recurse.c34
-rw-r--r--src/tests/userprog/multi-recurse.ck70
-rw-r--r--src/tests/userprog/no-vm/Make.tests8
-rw-r--r--src/tests/userprog/no-vm/Rubric3
-rw-r--r--src/tests/userprog/no-vm/multi-oom.c179
-rw-r--r--src/tests/userprog/no-vm/multi-oom.ck10
-rw-r--r--src/tests/userprog/null.ck8
-rw-r--r--src/tests/userprog/open-bad-ptr.c13
-rw-r--r--src/tests/userprog/open-bad-ptr.ck13
-rw-r--r--src/tests/userprog/open-boundary.c14
-rw-r--r--src/tests/userprog/open-boundary.ck11
-rw-r--r--src/tests/userprog/open-empty.c13
-rw-r--r--src/tests/userprog/open-empty.ck10
-rw-r--r--src/tests/userprog/open-missing.c13
-rw-r--r--src/tests/userprog/open-missing.ck10
-rw-r--r--src/tests/userprog/open-normal.c13
-rw-r--r--src/tests/userprog/open-normal.ck10
-rw-r--r--src/tests/userprog/open-null.c12
-rw-r--r--src/tests/userprog/open-null.ck13
-rw-r--r--src/tests/userprog/open-twice.c19
-rw-r--r--src/tests/userprog/open-twice.ck12
-rw-r--r--src/tests/userprog/read-bad-fd.c21
-rw-r--r--src/tests/userprog/read-bad-fd.ck13
-rw-r--r--src/tests/userprog/read-bad-ptr.c16
-rw-r--r--src/tests/userprog/read-bad-ptr.ck15
-rw-r--r--src/tests/userprog/read-boundary.c30
-rw-r--r--src/tests/userprog/read-boundary.ck11
-rw-r--r--src/tests/userprog/read-normal.c11
-rw-r--r--src/tests/userprog/read-normal.ck13
-rw-r--r--src/tests/userprog/read-stdout.c14
-rw-r--r--src/tests/userprog/read-stdout.ck13
-rw-r--r--src/tests/userprog/read-zero.c22
-rw-r--r--src/tests/userprog/read-zero.ck11
-rw-r--r--src/tests/userprog/rox-child.c5
-rw-r--r--src/tests/userprog/rox-child.ck20
-rw-r--r--src/tests/userprog/rox-child.inc33
-rw-r--r--src/tests/userprog/rox-multichild.c5
-rw-r--r--src/tests/userprog/rox-multichild.ck44
-rw-r--r--src/tests/userprog/rox-simple.c19
-rw-r--r--src/tests/userprog/rox-simple.ck13
-rw-r--r--src/tests/userprog/sample.inc6
-rw-r--r--src/tests/userprog/sample.txt4
-rw-r--r--src/tests/userprog/sc-bad-arg.c17
-rw-r--r--src/tests/userprog/sc-bad-arg.ck9
-rw-r--r--src/tests/userprog/sc-bad-sp.c20
-rw-r--r--src/tests/userprog/sc-bad-sp.ck9
-rw-r--r--src/tests/userprog/sc-boundary-2.c22
-rw-r--r--src/tests/userprog/sc-boundary-2.ck9
-rw-r--r--src/tests/userprog/sc-boundary.c22
-rw-r--r--src/tests/userprog/sc-boundary.ck9
-rw-r--r--src/tests/userprog/wait-bad-pid.c11
-rw-r--r--src/tests/userprog/wait-bad-pid.ck13
-rw-r--r--src/tests/userprog/wait-killed.c11
-rw-r--r--src/tests/userprog/wait-killed.ck13
-rw-r--r--src/tests/userprog/wait-simple.c11
-rw-r--r--src/tests/userprog/wait-simple.ck13
-rw-r--r--src/tests/userprog/wait-twice.c15
-rw-r--r--src/tests/userprog/wait-twice.ck14
-rw-r--r--src/tests/userprog/write-bad-fd.c20
-rw-r--r--src/tests/userprog/write-bad-fd.ck13
-rw-r--r--src/tests/userprog/write-bad-ptr.c16
-rw-r--r--src/tests/userprog/write-bad-ptr.ck15
-rw-r--r--src/tests/userprog/write-boundary.c25
-rw-r--r--src/tests/userprog/write-boundary.ck11
-rw-r--r--src/tests/userprog/write-normal.c20
-rw-r--r--src/tests/userprog/write-normal.ck12
-rw-r--r--src/tests/userprog/write-stdin.c14
-rw-r--r--src/tests/userprog/write-stdin.ck13
-rw-r--r--src/tests/userprog/write-zero.c20
-rw-r--r--src/tests/userprog/write-zero.ck11
142 files changed, 2556 insertions, 0 deletions
diff --git a/src/tests/userprog/Grading b/src/tests/userprog/Grading
new file mode 100644
index 0000000..f70dc99
--- /dev/null
+++ b/src/tests/userprog/Grading
@@ -0,0 +1,11 @@
+# Percentage of the testing point total designated for each set of
+# tests.
+
+# This project is primarily about implementing system calls.
+# If you do so properly, the base file system functionality
+# should come "for free". Thus, the points emphasis below.
+
+35% tests/userprog/Rubric.functionality
+25% tests/userprog/Rubric.robustness
+10% tests/userprog/no-vm/Rubric
+30% tests/filesys/base/Rubric
diff --git a/src/tests/userprog/Make.tests b/src/tests/userprog/Make.tests
new file mode 100644
index 0000000..1258582
--- /dev/null
+++ b/src/tests/userprog/Make.tests
@@ -0,0 +1,125 @@
+# -*- makefile -*-
+
+tests/%.output: FSDISK = 2
+tests/%.output: PUTFILES = $(filter-out os.dsk, $^)
+
+tests/userprog_TESTS = $(addprefix tests/userprog/,args-none \
+args-single args-multiple args-many args-dbl-space sc-bad-sp \
+sc-bad-arg sc-boundary sc-boundary-2 halt exit create-normal \
+create-empty create-null create-bad-ptr create-long create-exists \
+create-bound open-normal open-missing open-boundary open-empty \
+open-null open-bad-ptr open-twice close-normal close-twice close-stdin \
+close-stdout close-bad-fd read-normal read-bad-ptr read-boundary \
+read-zero read-stdout read-bad-fd write-normal write-bad-ptr \
+write-boundary write-zero write-stdin write-bad-fd exec-once exec-arg \
+exec-multiple exec-missing exec-bad-ptr wait-simple wait-twice \
+wait-killed wait-bad-pid multi-recurse multi-child-fd)
+
+
+
+tests/userprog_PROGS = $(tests/userprog_TESTS) $(addprefix \
+tests/userprog/,child-simple child-args child-bad child-close)
+
+
+tests/userprog/args-none_SRC = tests/userprog/args.c
+tests/userprog/args-single_SRC = tests/userprog/args.c
+tests/userprog/args-multiple_SRC = tests/userprog/args.c
+tests/userprog/args-many_SRC = tests/userprog/args.c
+tests/userprog/args-dbl-space_SRC = tests/userprog/args.c
+tests/userprog/sc-bad-sp_SRC = tests/userprog/sc-bad-sp.c tests/main.c
+tests/userprog/sc-bad-arg_SRC = tests/userprog/sc-bad-arg.c tests/main.c
+
+tests/userprog/sc-boundary_SRC = tests/userprog/sc-boundary.c \
+tests/userprog/boundary.c tests/main.c
+tests/userprog/sc-boundary-2_SRC = tests/userprog/sc-boundary-2.c \
+tests/userprog/boundary.c tests/main.c
+tests/userprog/halt_SRC = tests/userprog/halt.c tests/main.c
+tests/userprog/exit_SRC = tests/userprog/exit.c tests/main.c
+tests/userprog/create-normal_SRC = tests/userprog/create-normal.c tests/main.c
+tests/userprog/create-empty_SRC = tests/userprog/create-empty.c tests/main.c
+tests/userprog/create-null_SRC = tests/userprog/create-null.c tests/main.c
+tests/userprog/create-bad-ptr_SRC = tests/userprog/create-bad-ptr.c \
+tests/main.c
+tests/userprog/create-long_SRC = tests/userprog/create-long.c tests/main.c
+tests/userprog/create-exists_SRC = tests/userprog/create-exists.c tests/main.c
+tests/userprog/create-bound_SRC = tests/userprog/create-bound.c \
+tests/userprog/boundary.c tests/main.c
+tests/userprog/open-normal_SRC = tests/userprog/open-normal.c tests/main.c
+tests/userprog/open-missing_SRC = tests/userprog/open-missing.c tests/main.c
+tests/userprog/open-boundary_SRC = tests/userprog/open-boundary.c \
+tests/userprog/boundary.c tests/main.c
+tests/userprog/open-empty_SRC = tests/userprog/open-empty.c tests/main.c
+tests/userprog/open-null_SRC = tests/userprog/open-null.c tests/main.c
+tests/userprog/open-bad-ptr_SRC = tests/userprog/open-bad-ptr.c tests/main.c
+tests/userprog/open-twice_SRC = tests/userprog/open-twice.c tests/main.c
+tests/userprog/close-normal_SRC = tests/userprog/close-normal.c tests/main.c
+tests/userprog/close-twice_SRC = tests/userprog/close-twice.c tests/main.c
+tests/userprog/close-stdin_SRC = tests/userprog/close-stdin.c tests/main.c
+tests/userprog/close-stdout_SRC = tests/userprog/close-stdout.c tests/main.c
+tests/userprog/close-bad-fd_SRC = tests/userprog/close-bad-fd.c tests/main.c
+tests/userprog/read-normal_SRC = tests/userprog/read-normal.c tests/main.c
+tests/userprog/read-bad-ptr_SRC = tests/userprog/read-bad-ptr.c tests/main.c
+tests/userprog/read-boundary_SRC = tests/userprog/read-boundary.c \
+tests/userprog/boundary.c tests/main.c
+tests/userprog/read-zero_SRC = tests/userprog/read-zero.c tests/main.c
+tests/userprog/read-stdout_SRC = tests/userprog/read-stdout.c tests/main.c
+tests/userprog/read-bad-fd_SRC = tests/userprog/read-bad-fd.c tests/main.c
+tests/userprog/write-normal_SRC = tests/userprog/write-normal.c tests/main.c
+tests/userprog/write-bad-ptr_SRC = tests/userprog/write-bad-ptr.c tests/main.c
+tests/userprog/write-boundary_SRC = tests/userprog/write-boundary.c \
+tests/userprog/boundary.c tests/main.c
+tests/userprog/write-zero_SRC = tests/userprog/write-zero.c tests/main.c
+tests/userprog/write-stdin_SRC = tests/userprog/write-stdin.c tests/main.c
+tests/userprog/write-bad-fd_SRC = tests/userprog/write-bad-fd.c tests/main.c
+tests/userprog/exec-once_SRC = tests/userprog/exec-once.c tests/main.c
+tests/userprog/exec-arg_SRC = tests/userprog/exec-arg.c tests/main.c
+tests/userprog/exec-multiple_SRC = tests/userprog/exec-multiple.c tests/main.c
+tests/userprog/exec-missing_SRC = tests/userprog/exec-missing.c tests/main.c
+tests/userprog/exec-bad-ptr_SRC = tests/userprog/exec-bad-ptr.c tests/main.c
+tests/userprog/wait-simple_SRC = tests/userprog/wait-simple.c tests/main.c
+tests/userprog/wait-twice_SRC = tests/userprog/wait-twice.c tests/main.c
+tests/userprog/wait-killed_SRC = tests/userprog/wait-killed.c tests/main.c
+tests/userprog/wait-bad-pid_SRC = tests/userprog/wait-bad-pid.c tests/main.c
+tests/userprog/multi-recurse_SRC = tests/userprog/multi-recurse.c
+tests/userprog/multi-child-fd_SRC = tests/userprog/multi-child-fd.c \
+tests/main.c
+
+
+tests/userprog/child-simple_SRC = tests/userprog/child-simple.c
+tests/userprog/child-args_SRC = tests/userprog/args.c
+tests/userprog/child-bad_SRC = tests/userprog/child-bad.c tests/main.c
+tests/userprog/child-close_SRC = tests/userprog/child-close.c
+
+
+$(foreach prog,$(tests/userprog_PROGS),$(eval $(prog)_SRC += tests/lib.c))
+
+tests/userprog/args-single_ARGS = onearg
+tests/userprog/args-multiple_ARGS = some arguments for you!
+tests/userprog/args-many_ARGS = a b c d e f g h i j k l m n o p q r s t u v
+tests/userprog/args-dbl-space_ARGS = two spaces!
+tests/userprog/multi-recurse_ARGS = 15
+
+tests/userprog/open-normal_PUTFILES += tests/userprog/sample.txt
+tests/userprog/open-boundary_PUTFILES += tests/userprog/sample.txt
+tests/userprog/open-twice_PUTFILES += tests/userprog/sample.txt
+tests/userprog/close-normal_PUTFILES += tests/userprog/sample.txt
+tests/userprog/close-twice_PUTFILES += tests/userprog/sample.txt
+tests/userprog/read-normal_PUTFILES += tests/userprog/sample.txt
+tests/userprog/read-bad-ptr_PUTFILES += tests/userprog/sample.txt
+tests/userprog/read-boundary_PUTFILES += tests/userprog/sample.txt
+tests/userprog/read-zero_PUTFILES += tests/userprog/sample.txt
+tests/userprog/write-normal_PUTFILES += tests/userprog/sample.txt
+tests/userprog/write-bad-ptr_PUTFILES += tests/userprog/sample.txt
+tests/userprog/write-boundary_PUTFILES += tests/userprog/sample.txt
+tests/userprog/write-zero_PUTFILES += tests/userprog/sample.txt
+tests/userprog/multi-child-fd_PUTFILES += tests/userprog/sample.txt
+
+tests/userprog/exec-once_PUTFILES += tests/userprog/child-simple
+tests/userprog/exec-multiple_PUTFILES += tests/userprog/child-simple
+tests/userprog/wait-simple_PUTFILES += tests/userprog/child-simple
+tests/userprog/wait-twice_PUTFILES += tests/userprog/child-simple
+
+tests/userprog/exec-arg_PUTFILES += tests/userprog/child-args
+tests/userprog/multi-child-fd_PUTFILES += tests/userprog/child-close
+tests/userprog/wait-killed_PUTFILES += tests/userprog/child-bad
+
diff --git a/src/tests/userprog/Make.tests.odig b/src/tests/userprog/Make.tests.odig
new file mode 100644
index 0000000..c762af3
--- /dev/null
+++ b/src/tests/userprog/Make.tests.odig
@@ -0,0 +1,132 @@
+# -*- makefile -*-
+
+tests/%.output: FSDISK = 2
+tests/%.output: PUTFILES = $(filter-out os.dsk, $^)
+
+tests/userprog_TESTS = $(addprefix tests/userprog/,args-none \
+args-single args-multiple args-many args-dbl-space sc-bad-sp \
+sc-bad-arg sc-boundary sc-boundary-2 halt exit create-normal \
+create-empty create-null create-bad-ptr create-long create-exists \
+create-bound open-normal open-missing open-boundary open-empty \
+open-null open-bad-ptr open-twice close-normal close-stdin \
+close-stdout close-bad-fd read-bad-ptr read-boundary \
+read-zero read-stdout read-bad-fd write-normal write-bad-ptr \
+write-boundary write-zero write-stdin write-bad-fd exec-once exec-arg \
+exec-multiple exec-missing exec-bad-ptr wait-simple wait-twice \
+wait-killed wait-bad-pid multi-recurse \
+)
+
+tests/userprog_PROGS = $(tests/userprog_TESTS) $(addprefix \
+tests/userprog/,child-simple child-args child-bad child-close child-rox)
+
+tests/userprog/args-none_SRC = tests/userprog/args.c
+tests/userprog/args-single_SRC = tests/userprog/args.c
+tests/userprog/args-multiple_SRC = tests/userprog/args.c
+tests/userprog/args-many_SRC = tests/userprog/args.c
+tests/userprog/args-dbl-space_SRC = tests/userprog/args.c
+tests/userprog/sc-bad-sp_SRC = tests/userprog/sc-bad-sp.c tests/main.c
+tests/userprog/sc-bad-arg_SRC = tests/userprog/sc-bad-arg.c tests/main.c
+tests/userprog/bad-read_SRC = tests/userprog/bad-read.c tests/main.c
+tests/userprog/bad-write_SRC = tests/userprog/bad-write.c tests/main.c
+tests/userprog/bad-jump_SRC = tests/userprog/bad-jump.c tests/main.c
+tests/userprog/bad-read2_SRC = tests/userprog/bad-read2.c tests/main.c
+tests/userprog/bad-write2_SRC = tests/userprog/bad-write2.c tests/main.c
+tests/userprog/bad-jump2_SRC = tests/userprog/bad-jump2.c tests/main.c
+tests/userprog/sc-boundary_SRC = tests/userprog/sc-boundary.c \
+tests/userprog/boundary.c tests/main.c
+tests/userprog/sc-boundary-2_SRC = tests/userprog/sc-boundary-2.c \
+tests/userprog/boundary.c tests/main.c
+tests/userprog/halt_SRC = tests/userprog/halt.c tests/main.c
+tests/userprog/exit_SRC = tests/userprog/exit.c tests/main.c
+tests/userprog/create-normal_SRC = tests/userprog/create-normal.c tests/main.c
+tests/userprog/create-empty_SRC = tests/userprog/create-empty.c tests/main.c
+tests/userprog/create-null_SRC = tests/userprog/create-null.c tests/main.c
+tests/userprog/create-bad-ptr_SRC = tests/userprog/create-bad-ptr.c \
+tests/main.c
+tests/userprog/create-long_SRC = tests/userprog/create-long.c tests/main.c
+tests/userprog/create-exists_SRC = tests/userprog/create-exists.c tests/main.c
+tests/userprog/create-bound_SRC = tests/userprog/create-bound.c \
+tests/userprog/boundary.c tests/main.c
+tests/userprog/open-normal_SRC = tests/userprog/open-normal.c tests/main.c
+tests/userprog/open-missing_SRC = tests/userprog/open-missing.c tests/main.c
+tests/userprog/open-boundary_SRC = tests/userprog/open-boundary.c \
+tests/userprog/boundary.c tests/main.c
+tests/userprog/open-empty_SRC = tests/userprog/open-empty.c tests/main.c
+tests/userprog/open-null_SRC = tests/userprog/open-null.c tests/main.c
+tests/userprog/open-bad-ptr_SRC = tests/userprog/open-bad-ptr.c tests/main.c
+tests/userprog/open-twice_SRC = tests/userprog/open-twice.c tests/main.c
+tests/userprog/close-normal_SRC = tests/userprog/close-normal.c tests/main.c
+tests/userprog/close-twice_SRC = tests/userprog/close-twice.c tests/main.c
+tests/userprog/close-stdin_SRC = tests/userprog/close-stdin.c tests/main.c
+tests/userprog/close-stdout_SRC = tests/userprog/close-stdout.c tests/main.c
+tests/userprog/close-bad-fd_SRC = tests/userprog/close-bad-fd.c tests/main.c
+tests/userprog/read-normal_SRC = tests/userprog/read-normal.c tests/main.c
+tests/userprog/read-bad-ptr_SRC = tests/userprog/read-bad-ptr.c tests/main.c
+tests/userprog/read-boundary_SRC = tests/userprog/read-boundary.c \
+tests/userprog/boundary.c tests/main.c
+tests/userprog/read-zero_SRC = tests/userprog/read-zero.c tests/main.c
+tests/userprog/read-stdout_SRC = tests/userprog/read-stdout.c tests/main.c
+tests/userprog/read-bad-fd_SRC = tests/userprog/read-bad-fd.c tests/main.c
+tests/userprog/write-normal_SRC = tests/userprog/write-normal.c tests/main.c
+tests/userprog/write-bad-ptr_SRC = tests/userprog/write-bad-ptr.c tests/main.c
+tests/userprog/write-boundary_SRC = tests/userprog/write-boundary.c \
+tests/userprog/boundary.c tests/main.c
+tests/userprog/write-zero_SRC = tests/userprog/write-zero.c tests/main.c
+tests/userprog/write-stdin_SRC = tests/userprog/write-stdin.c tests/main.c
+tests/userprog/write-bad-fd_SRC = tests/userprog/write-bad-fd.c tests/main.c
+tests/userprog/exec-once_SRC = tests/userprog/exec-once.c tests/main.c
+tests/userprog/exec-arg_SRC = tests/userprog/exec-arg.c tests/main.c
+tests/userprog/exec-multiple_SRC = tests/userprog/exec-multiple.c tests/main.c
+tests/userprog/exec-missing_SRC = tests/userprog/exec-missing.c tests/main.c
+tests/userprog/exec-bad-ptr_SRC = tests/userprog/exec-bad-ptr.c tests/main.c
+tests/userprog/wait-simple_SRC = tests/userprog/wait-simple.c tests/main.c
+tests/userprog/wait-twice_SRC = tests/userprog/wait-twice.c tests/main.c
+tests/userprog/wait-killed_SRC = tests/userprog/wait-killed.c tests/main.c
+tests/userprog/wait-bad-pid_SRC = tests/userprog/wait-bad-pid.c tests/main.c
+tests/userprog/multi-recurse_SRC = tests/userprog/multi-recurse.c
+tests/userprog/multi-child-fd_SRC = tests/userprog/multi-child-fd.c \
+tests/main.c
+tests/userprog/rox-simple_SRC = tests/userprog/rox-simple.c tests/main.c
+tests/userprog/rox-child_SRC = tests/userprog/rox-child.c tests/main.c
+tests/userprog/rox-multichild_SRC = tests/userprog/rox-multichild.c \
+tests/main.c
+
+tests/userprog/child-simple_SRC = tests/userprog/child-simple.c
+tests/userprog/child-args_SRC = tests/userprog/args.c
+tests/userprog/child-bad_SRC = tests/userprog/child-bad.c tests/main.c
+tests/userprog/child-close_SRC = tests/userprog/child-close.c
+tests/userprog/child-rox_SRC = tests/userprog/child-rox.c
+
+$(foreach prog,$(tests/userprog_PROGS),$(eval $(prog)_SRC += tests/lib.c))
+
+tests/userprog/args-single_ARGS = onearg
+tests/userprog/args-multiple_ARGS = some arguments for you!
+tests/userprog/args-many_ARGS = a b c d e f g h i j k l m n o p q r s t u v
+tests/userprog/args-dbl-space_ARGS = two spaces!
+tests/userprog/multi-recurse_ARGS = 15
+
+tests/userprog/open-normal_PUTFILES += tests/userprog/sample.txt
+tests/userprog/open-boundary_PUTFILES += tests/userprog/sample.txt
+tests/userprog/open-twice_PUTFILES += tests/userprog/sample.txt
+tests/userprog/close-normal_PUTFILES += tests/userprog/sample.txt
+tests/userprog/close-twice_PUTFILES += tests/userprog/sample.txt
+tests/userprog/read-normal_PUTFILES += tests/userprog/sample.txt
+tests/userprog/read-bad-ptr_PUTFILES += tests/userprog/sample.txt
+tests/userprog/read-boundary_PUTFILES += tests/userprog/sample.txt
+tests/userprog/read-zero_PUTFILES += tests/userprog/sample.txt
+tests/userprog/write-normal_PUTFILES += tests/userprog/sample.txt
+tests/userprog/write-bad-ptr_PUTFILES += tests/userprog/sample.txt
+tests/userprog/write-boundary_PUTFILES += tests/userprog/sample.txt
+tests/userprog/write-zero_PUTFILES += tests/userprog/sample.txt
+tests/userprog/multi-child-fd_PUTFILES += tests/userprog/sample.txt
+
+tests/userprog/exec-once_PUTFILES += tests/userprog/child-simple
+tests/userprog/exec-multiple_PUTFILES += tests/userprog/child-simple
+tests/userprog/wait-simple_PUTFILES += tests/userprog/child-simple
+tests/userprog/wait-twice_PUTFILES += tests/userprog/child-simple
+
+tests/userprog/exec-arg_PUTFILES += tests/userprog/child-args
+tests/userprog/multi-child-fd_PUTFILES += tests/userprog/child-close
+tests/userprog/wait-killed_PUTFILES += tests/userprog/child-bad
+tests/userprog/rox-child_PUTFILES += tests/userprog/child-rox
+tests/userprog/rox-multichild_PUTFILES += tests/userprog/child-rox
diff --git a/src/tests/userprog/Rubric.functionality b/src/tests/userprog/Rubric.functionality
new file mode 100644
index 0000000..ea76c44
--- /dev/null
+++ b/src/tests/userprog/Rubric.functionality
@@ -0,0 +1,52 @@
+Functionality of system calls:
+- Test argument passing on Pintos command line.
+3 args-none
+3 args-single
+3 args-multiple
+3 args-many
+3 args-dbl-space
+
+- Test "create" system call.
+3 create-empty
+3 create-long
+3 create-normal
+3 create-exists
+
+- Test "open" system call.
+3 open-missing
+3 open-normal
+3 open-twice
+
+- Test "read" system call.
+3 read-normal
+3 read-zero
+
+- Test "write" system call.
+3 write-normal
+3 write-zero
+
+- Test "close" system call.
+3 close-normal
+
+- Test "exec" system call.
+5 exec-once
+5 exec-multiple
+5 exec-arg
+
+- Test "wait" system call.
+5 wait-simple
+5 wait-twice
+
+- Test "exit" system call.
+5 exit
+
+- Test "halt" system call.
+3 halt
+
+- Test recursive execution of user programs.
+15 multi-recurse
+
+- Test read-only executable feature.
+3 rox-simple
+3 rox-child
+3 rox-multichild
diff --git a/src/tests/userprog/Rubric.robustness b/src/tests/userprog/Rubric.robustness
new file mode 100644
index 0000000..b7d1035
--- /dev/null
+++ b/src/tests/userprog/Rubric.robustness
@@ -0,0 +1,48 @@
+Robustness of system calls:
+- Test robustness of file descriptor handling.
+2 close-stdin
+2 close-stdout
+2 close-bad-fd
+2 close-twice
+2 read-bad-fd
+2 read-stdout
+2 write-bad-fd
+2 write-stdin
+2 multi-child-fd
+
+- Test robustness of pointer handling.
+3 create-bad-ptr
+3 exec-bad-ptr
+3 open-bad-ptr
+3 read-bad-ptr
+3 write-bad-ptr
+
+- Test robustness of buffer copying across page boundaries.
+3 create-bound
+3 open-boundary
+3 read-boundary
+3 write-boundary
+
+- Test handling of null pointer and empty strings.
+2 create-null
+2 open-null
+2 open-empty
+
+- Test robustness of system call implementation.
+3 sc-bad-arg
+3 sc-bad-sp
+5 sc-boundary
+5 sc-boundary-2
+
+- Test robustness of "exec" and "wait" system calls.
+5 exec-missing
+5 wait-bad-pid
+5 wait-killed
+
+- Test robustness of exception handling.
+1 bad-read
+1 bad-write
+1 bad-jump
+1 bad-read2
+1 bad-write2
+1 bad-jump2
diff --git a/src/tests/userprog/args-dbl-space.ck b/src/tests/userprog/args-dbl-space.ck
new file mode 100644
index 0000000..dfbcf4b
--- /dev/null
+++ b/src/tests/userprog/args-dbl-space.ck
@@ -0,0 +1,15 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(args) begin
+(args) argc = 3
+(args) argv[0] = 'args-dbl-space'
+(args) argv[1] = 'two'
+(args) argv[2] = 'spaces!'
+(args) argv[3] = null
+(args) end
+args-dbl-space: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/args-many.ck b/src/tests/userprog/args-many.ck
new file mode 100644
index 0000000..214574a
--- /dev/null
+++ b/src/tests/userprog/args-many.ck
@@ -0,0 +1,35 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(args) begin
+(args) argc = 23
+(args) argv[0] = 'args-many'
+(args) argv[1] = 'a'
+(args) argv[2] = 'b'
+(args) argv[3] = 'c'
+(args) argv[4] = 'd'
+(args) argv[5] = 'e'
+(args) argv[6] = 'f'
+(args) argv[7] = 'g'
+(args) argv[8] = 'h'
+(args) argv[9] = 'i'
+(args) argv[10] = 'j'
+(args) argv[11] = 'k'
+(args) argv[12] = 'l'
+(args) argv[13] = 'm'
+(args) argv[14] = 'n'
+(args) argv[15] = 'o'
+(args) argv[16] = 'p'
+(args) argv[17] = 'q'
+(args) argv[18] = 'r'
+(args) argv[19] = 's'
+(args) argv[20] = 't'
+(args) argv[21] = 'u'
+(args) argv[22] = 'v'
+(args) argv[23] = null
+(args) end
+args-many: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/args-multiple.ck b/src/tests/userprog/args-multiple.ck
new file mode 100644
index 0000000..227e6cc
--- /dev/null
+++ b/src/tests/userprog/args-multiple.ck
@@ -0,0 +1,17 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(args) begin
+(args) argc = 5
+(args) argv[0] = 'args-multiple'
+(args) argv[1] = 'some'
+(args) argv[2] = 'arguments'
+(args) argv[3] = 'for'
+(args) argv[4] = 'you!'
+(args) argv[5] = null
+(args) end
+args-multiple: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/args-none.ck b/src/tests/userprog/args-none.ck
new file mode 100644
index 0000000..146318e
--- /dev/null
+++ b/src/tests/userprog/args-none.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(args) begin
+(args) argc = 1
+(args) argv[0] = 'args-none'
+(args) argv[1] = null
+(args) end
+args-none: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/args-single.ck b/src/tests/userprog/args-single.ck
new file mode 100644
index 0000000..24582b4
--- /dev/null
+++ b/src/tests/userprog/args-single.ck
@@ -0,0 +1,14 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(args) begin
+(args) argc = 2
+(args) argv[0] = 'args-single'
+(args) argv[1] = 'onearg'
+(args) argv[2] = null
+(args) end
+args-single: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/args.c b/src/tests/userprog/args.c
new file mode 100644
index 0000000..20eda44
--- /dev/null
+++ b/src/tests/userprog/args.c
@@ -0,0 +1,25 @@
+/* Prints the command-line arguments.
+ This program is used for all of the args-* tests. Grading is
+ done differently for each of the args-* tests based on the
+ output. */
+
+#include "tests/lib.h"
+
+int
+main (int argc, char *argv[])
+{
+ int i;
+
+ test_name = "args";
+
+ msg ("begin");
+ msg ("argc = %d", argc);
+ for (i = 0; i <= argc; i++)
+ if (argv[i] != NULL)
+ msg ("argv[%d] = '%s'", i, argv[i]);
+ else
+ msg ("argv[%d] = null", i);
+ msg ("end");
+
+ return 0;
+}
diff --git a/src/tests/userprog/bad-jump.c b/src/tests/userprog/bad-jump.c
new file mode 100644
index 0000000..51b7c9f
--- /dev/null
+++ b/src/tests/userprog/bad-jump.c
@@ -0,0 +1,13 @@
+/* This program attempts to execute code at address 0, which is not mapped.
+ This should terminate the process with a -1 exit code. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ msg ("Congratulations - you have successfully called NULL: %d",
+ ((int (*)(void))NULL)());
+ fail ("should have exited with -1");
+}
diff --git a/src/tests/userprog/bad-jump.ck b/src/tests/userprog/bad-jump.ck
new file mode 100644
index 0000000..e1c178b
--- /dev/null
+++ b/src/tests/userprog/bad-jump.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']);
+(bad-jump) begin
+bad-jump: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/bad-jump2.c b/src/tests/userprog/bad-jump2.c
new file mode 100644
index 0000000..dc7c2a7
--- /dev/null
+++ b/src/tests/userprog/bad-jump2.c
@@ -0,0 +1,13 @@
+/* This program attempts to execute code at a kernel virtual address.
+ This should terminate the process with a -1 exit code. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ msg ("Congratulations - you have successfully called kernel code: %d",
+ ((int (*)(void))0xC0000000)());
+ fail ("should have exited with -1");
+}
diff --git a/src/tests/userprog/bad-jump2.ck b/src/tests/userprog/bad-jump2.ck
new file mode 100644
index 0000000..35f0f97
--- /dev/null
+++ b/src/tests/userprog/bad-jump2.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']);
+(bad-jump2) begin
+bad-jump2: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/bad-read.c b/src/tests/userprog/bad-read.c
new file mode 100644
index 0000000..904c278
--- /dev/null
+++ b/src/tests/userprog/bad-read.c
@@ -0,0 +1,13 @@
+/* This program attempts to read memory at an address that is not mapped.
+ This should terminate the process with a -1 exit code. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ msg ("Congratulations - you have successfully dereferenced NULL: %d",
+ *(int *)NULL);
+ fail ("should have exited with -1");
+}
diff --git a/src/tests/userprog/bad-read.ck b/src/tests/userprog/bad-read.ck
new file mode 100644
index 0000000..4d4d926
--- /dev/null
+++ b/src/tests/userprog/bad-read.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']);
+(bad-read) begin
+bad-read: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/bad-read2.c b/src/tests/userprog/bad-read2.c
new file mode 100644
index 0000000..a2fc237
--- /dev/null
+++ b/src/tests/userprog/bad-read2.c
@@ -0,0 +1,13 @@
+/* This program attempts to read kernel memory.
+ This should terminate the process with a -1 exit code. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ msg ("Congratulations - you have successfully read kernel memory: %d",
+ *(int *)0xC0000000);
+ fail ("should have exited with -1");
+}
diff --git a/src/tests/userprog/bad-read2.ck b/src/tests/userprog/bad-read2.ck
new file mode 100644
index 0000000..fa27c7d
--- /dev/null
+++ b/src/tests/userprog/bad-read2.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']);
+(bad-read2) begin
+bad-read2: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/bad-write.c b/src/tests/userprog/bad-write.c
new file mode 100644
index 0000000..000c26b
--- /dev/null
+++ b/src/tests/userprog/bad-write.c
@@ -0,0 +1,12 @@
+/* This program attempts to write to memory at an address that is not mapped.
+ This should terminate the process with a -1 exit code. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ *(int *)NULL = 42;
+ fail ("should have exited with -1");
+}
diff --git a/src/tests/userprog/bad-write.ck b/src/tests/userprog/bad-write.ck
new file mode 100644
index 0000000..d213b49
--- /dev/null
+++ b/src/tests/userprog/bad-write.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']);
+(bad-write) begin
+bad-write: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/bad-write2.c b/src/tests/userprog/bad-write2.c
new file mode 100644
index 0000000..753da1e
--- /dev/null
+++ b/src/tests/userprog/bad-write2.c
@@ -0,0 +1,12 @@
+/* This program attempts to write to kernel memory.
+ This should terminate the process with a -1 exit code. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ *(int *)0xC0000000 = 42;
+ fail ("should have exited with -1");
+}
diff --git a/src/tests/userprog/bad-write2.ck b/src/tests/userprog/bad-write2.ck
new file mode 100644
index 0000000..c6a3420
--- /dev/null
+++ b/src/tests/userprog/bad-write2.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']);
+(bad-write2) begin
+bad-write2: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/boundary.c b/src/tests/userprog/boundary.c
new file mode 100644
index 0000000..59907ec
--- /dev/null
+++ b/src/tests/userprog/boundary.c
@@ -0,0 +1,33 @@
+/* Utility function for tests that try to break system calls by
+ passing them data that crosses from one virtual page to
+ another. */
+
+#include <inttypes.h>
+#include <round.h>
+#include <string.h>
+#include "tests/userprog/boundary.h"
+
+static char dst[8192];
+
+/* Returns the beginning of a page. There are at least 2048
+ modifiable bytes on either side of the pointer returned. */
+void *
+get_boundary_area (void)
+{
+ char *p = (char *) ROUND_UP ((uintptr_t) dst, 4096);
+ if (p - dst < 2048)
+ p += 4096;
+ return p;
+}
+
+/* Returns a copy of SRC split across the boundary between two
+ pages. */
+char *
+copy_string_across_boundary (const char *src)
+{
+ char *p = get_boundary_area ();
+ p -= strlen (src) < 4096 ? strlen (src) / 2 : 4096;
+ strlcpy (p, src, 4096);
+ return p;
+}
+
diff --git a/src/tests/userprog/boundary.h b/src/tests/userprog/boundary.h
new file mode 100644
index 0000000..c8e4b3b
--- /dev/null
+++ b/src/tests/userprog/boundary.h
@@ -0,0 +1,7 @@
+#ifndef TESTS_USERPROG_BOUNDARY_H
+#define TESTS_USERPROG_BOUNDARY_H
+
+void *get_boundary_area (void);
+char *copy_string_across_boundary (const char *);
+
+#endif /* tests/userprog/boundary.h */
diff --git a/src/tests/userprog/child-bad.c b/src/tests/userprog/child-bad.c
new file mode 100644
index 0000000..77d7a69
--- /dev/null
+++ b/src/tests/userprog/child-bad.c
@@ -0,0 +1,14 @@
+/* Child process run by wait-killed test.
+ Sets the stack pointer (%esp) to an invalid value and invokes
+ a system call, which should then terminate the process with a
+ -1 exit code. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ asm volatile ("movl $0x20101234, %esp; int $0x30");
+ fail ("should have exited with -1");
+}
diff --git a/src/tests/userprog/child-close.c b/src/tests/userprog/child-close.c
new file mode 100644
index 0000000..ac948c8
--- /dev/null
+++ b/src/tests/userprog/child-close.c
@@ -0,0 +1,28 @@
+/* Child process run by multi-child-fd test.
+
+ Attempts to close the file descriptor passed as the first
+ command-line argument. This is invalid, because file
+ descriptors are not inherited in Pintos. Two results are
+ allowed: either the system call should return without taking
+ any action, or the kernel should terminate the process with a
+ -1 exit code. */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <syscall.h>
+#include "tests/lib.h"
+
+const char *test_name = "child-close";
+
+int
+main (int argc UNUSED, char *argv[])
+{
+ msg ("begin");
+ if (!isdigit (*argv[1]))
+ fail ("bad command-line arguments");
+ close (atoi (argv[1]));
+ msg ("end");
+
+ return 0;
+}
diff --git a/src/tests/userprog/child-rox.c b/src/tests/userprog/child-rox.c
new file mode 100644
index 0000000..aba808b
--- /dev/null
+++ b/src/tests/userprog/child-rox.c
@@ -0,0 +1,55 @@
+/* Child process run by rox-child and rox-multichild tests.
+ Opens and tries to write to its own executable, verifying that
+ that is disallowed.
+ Then recursively executes itself to the depth indicated by the
+ first command-line argument. */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <syscall.h>
+#include "tests/lib.h"
+
+const char *test_name = "child-rox";
+
+static void
+try_write (void)
+{
+ int handle;
+ char buffer[19];
+
+ quiet = true;
+ CHECK ((handle = open ("child-rox")) > 1, "open \"child-rox\"");
+ quiet = false;
+
+ CHECK (write (handle, buffer, sizeof buffer) == 0,
+ "try to write \"child-rox\"");
+
+ close (handle);
+}
+
+int
+main (int argc UNUSED, char *argv[])
+{
+ msg ("begin");
+ try_write ();
+
+ if (!isdigit (*argv[1]))
+ fail ("bad command-line arguments");
+ if (atoi (argv[1]) > 1)
+ {
+ char cmd[128];
+ int child;
+
+ snprintf (cmd, sizeof cmd, "child-rox %d", atoi (argv[1]) - 1);
+ CHECK ((child = exec (cmd)) != -1, "exec \"%s\"", cmd);
+ quiet = true;
+ CHECK (wait (child) == 12, "wait for \"child-rox\"");
+ quiet = false;
+ }
+
+ try_write ();
+ msg ("end");
+
+ return 12;
+}
diff --git a/src/tests/userprog/child-simple.c b/src/tests/userprog/child-simple.c
new file mode 100644
index 0000000..0d2dacf
--- /dev/null
+++ b/src/tests/userprog/child-simple.c
@@ -0,0 +1,15 @@
+/* Child process run by exec-multiple, exec-one, wait-simple, and
+ wait-twice tests.
+ Just prints a single message and terminates. */
+
+#include <stdio.h>
+#include "tests/lib.h"
+
+const char *test_name = "child-simple";
+
+int
+main (void)
+{
+ msg ("run");
+ return 81;
+}
diff --git a/src/tests/userprog/close-bad-fd.c b/src/tests/userprog/close-bad-fd.c
new file mode 100644
index 0000000..f63bb9a
--- /dev/null
+++ b/src/tests/userprog/close-bad-fd.c
@@ -0,0 +1,11 @@
+/* Tries to close an invalid fd, which must either fail silently
+ or terminate with exit code -1. */
+
+#include <syscall.h>
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ close (0x20101234);
+}
diff --git a/src/tests/userprog/close-bad-fd.ck b/src/tests/userprog/close-bad-fd.ck
new file mode 100644
index 0000000..497b17c
--- /dev/null
+++ b/src/tests/userprog/close-bad-fd.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(close-bad-fd) begin
+(close-bad-fd) end
+close-bad-fd: exit(0)
+EOF
+(close-bad-fd) begin
+close-bad-fd: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/close-normal.c b/src/tests/userprog/close-normal.c
new file mode 100644
index 0000000..8ce04e3
--- /dev/null
+++ b/src/tests/userprog/close-normal.c
@@ -0,0 +1,14 @@
+/* Opens a file and then closes it. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ int handle;
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ msg ("close \"sample.txt\"");
+ close (handle);
+}
diff --git a/src/tests/userprog/close-normal.ck b/src/tests/userprog/close-normal.ck
new file mode 100644
index 0000000..fe41342
--- /dev/null
+++ b/src/tests/userprog/close-normal.ck
@@ -0,0 +1,12 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(close-normal) begin
+(close-normal) open "sample.txt"
+(close-normal) close "sample.txt"
+(close-normal) end
+close-normal: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/close-stdin.c b/src/tests/userprog/close-stdin.c
new file mode 100644
index 0000000..9bbf9f2
--- /dev/null
+++ b/src/tests/userprog/close-stdin.c
@@ -0,0 +1,11 @@
+/* Tries to close the keyboard input stream, which must either
+ fail silently or terminate with exit code -1. */
+
+#include <syscall.h>
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ close (0);
+}
diff --git a/src/tests/userprog/close-stdin.ck b/src/tests/userprog/close-stdin.ck
new file mode 100644
index 0000000..3d28507
--- /dev/null
+++ b/src/tests/userprog/close-stdin.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(close-stdin) begin
+(close-stdin) end
+close-stdin: exit(0)
+EOF
+(close-stdin) begin
+close-stdin: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/close-stdout.c b/src/tests/userprog/close-stdout.c
new file mode 100644
index 0000000..886523f
--- /dev/null
+++ b/src/tests/userprog/close-stdout.c
@@ -0,0 +1,11 @@
+/* Tries to close the console output stream, which must either
+ fail silently or terminate with exit code -1. */
+
+#include <syscall.h>
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ close (1);
+}
diff --git a/src/tests/userprog/close-stdout.ck b/src/tests/userprog/close-stdout.ck
new file mode 100644
index 0000000..3cbbcff
--- /dev/null
+++ b/src/tests/userprog/close-stdout.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(close-stdout) begin
+(close-stdout) end
+close-stdout: exit(0)
+EOF
+(close-stdout) begin
+close-stdout: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/close-twice.c b/src/tests/userprog/close-twice.c
new file mode 100644
index 0000000..830bccf
--- /dev/null
+++ b/src/tests/userprog/close-twice.c
@@ -0,0 +1,18 @@
+/* Opens a file and then tries to close it twice. The second
+ close must either fail silently or terminate with exit code
+ -1. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ int handle;
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ msg ("close \"sample.txt\"");
+ close (handle);
+ msg ("close \"sample.txt\" again");
+ close (handle);
+}
diff --git a/src/tests/userprog/close-twice.ck b/src/tests/userprog/close-twice.ck
new file mode 100644
index 0000000..deb55a6
--- /dev/null
+++ b/src/tests/userprog/close-twice.ck
@@ -0,0 +1,19 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(close-twice) begin
+(close-twice) open "sample.txt"
+(close-twice) close "sample.txt"
+(close-twice) close "sample.txt" again
+(close-twice) end
+close-twice: exit(0)
+EOF
+(close-twice) begin
+(close-twice) open "sample.txt"
+(close-twice) close "sample.txt"
+(close-twice) close "sample.txt" again
+close-twice: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/create-bad-ptr.c b/src/tests/userprog/create-bad-ptr.c
new file mode 100644
index 0000000..4a07bb3
--- /dev/null
+++ b/src/tests/userprog/create-bad-ptr.c
@@ -0,0 +1,12 @@
+/* Passes a bad pointer to the create system call,
+ which must cause the process to be terminated with exit code
+ -1. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ msg ("create(0x20101234): %d", create ((char *) 0x20101234, 0));
+}
diff --git a/src/tests/userprog/create-bad-ptr.ck b/src/tests/userprog/create-bad-ptr.ck
new file mode 100644
index 0000000..ac13405
--- /dev/null
+++ b/src/tests/userprog/create-bad-ptr.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(create-bad-ptr) begin
+create-bad-ptr: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/create-bound.c b/src/tests/userprog/create-bound.c
new file mode 100644
index 0000000..0a829f3
--- /dev/null
+++ b/src/tests/userprog/create-bound.c
@@ -0,0 +1,14 @@
+/* Opens a file whose name spans the boundary between two pages.
+ This is valid, so it must succeed. */
+
+#include <syscall.h>
+#include "tests/userprog/boundary.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ msg ("create(\"quux.dat\"): %d",
+ create (copy_string_across_boundary ("quux.dat"), 0));
+}
diff --git a/src/tests/userprog/create-bound.ck b/src/tests/userprog/create-bound.ck
new file mode 100644
index 0000000..7656b7f
--- /dev/null
+++ b/src/tests/userprog/create-bound.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(create-bound) begin
+(create-bound) create("quux.dat"): 1
+(create-bound) end
+create-bound: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/create-empty.c b/src/tests/userprog/create-empty.c
new file mode 100644
index 0000000..fa26b43
--- /dev/null
+++ b/src/tests/userprog/create-empty.c
@@ -0,0 +1,10 @@
+/* Tries to create a file with the empty string as its name. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ msg ("create(\"\"): %d", create ("", 0));
+}
diff --git a/src/tests/userprog/create-empty.ck b/src/tests/userprog/create-empty.ck
new file mode 100644
index 0000000..93a1058
--- /dev/null
+++ b/src/tests/userprog/create-empty.ck
@@ -0,0 +1,14 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(create-empty) begin
+(create-empty) create(""): 0
+(create-empty) end
+create-empty: exit(0)
+EOF
+(create-empty) begin
+create-empty: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/create-exists.c b/src/tests/userprog/create-exists.c
new file mode 100644
index 0000000..d395008
--- /dev/null
+++ b/src/tests/userprog/create-exists.c
@@ -0,0 +1,16 @@
+/* Verifies that trying to create a file under a name that
+ already exists will fail. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ CHECK (create ("quux.dat", 0), "create quux.dat");
+ CHECK (create ("warble.dat", 0), "create warble.dat");
+ CHECK (!create ("quux.dat", 0), "try to re-create quux.dat");
+ CHECK (create ("baffle.dat", 0), "create baffle.dat");
+ CHECK (!create ("warble.dat", 0), "try to re-create quux.dat");
+}
diff --git a/src/tests/userprog/create-exists.ck b/src/tests/userprog/create-exists.ck
new file mode 100644
index 0000000..006885e
--- /dev/null
+++ b/src/tests/userprog/create-exists.ck
@@ -0,0 +1,15 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(create-exists) begin
+(create-exists) create quux.dat
+(create-exists) create warble.dat
+(create-exists) try to re-create quux.dat
+(create-exists) create baffle.dat
+(create-exists) try to re-create quux.dat
+(create-exists) end
+create-exists: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/create-long.c b/src/tests/userprog/create-long.c
new file mode 100644
index 0000000..16b31bd
--- /dev/null
+++ b/src/tests/userprog/create-long.c
@@ -0,0 +1,17 @@
+/* Tries to create a file with a name that is much too long,
+ which must fail. */
+
+#include <string.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ static char name[512];
+ memset (name, 'x', sizeof name);
+ name[sizeof name - 1] = '\0';
+
+ msg ("create(\"x...\"): %d", create (name, 0));
+}
diff --git a/src/tests/userprog/create-long.ck b/src/tests/userprog/create-long.ck
new file mode 100644
index 0000000..628411c
--- /dev/null
+++ b/src/tests/userprog/create-long.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(create-long) begin
+(create-long) create("x..."): 0
+(create-long) end
+create-long: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/create-normal.c b/src/tests/userprog/create-normal.c
new file mode 100644
index 0000000..3cbc463
--- /dev/null
+++ b/src/tests/userprog/create-normal.c
@@ -0,0 +1,10 @@
+/* Creates an ordinary empty file. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ CHECK (create ("quux.dat", 0), "create quux.dat");
+}
diff --git a/src/tests/userprog/create-normal.ck b/src/tests/userprog/create-normal.ck
new file mode 100644
index 0000000..ca74a6e
--- /dev/null
+++ b/src/tests/userprog/create-normal.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(create-normal) begin
+(create-normal) create quux.dat
+(create-normal) end
+create-normal: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/create-null.c b/src/tests/userprog/create-null.c
new file mode 100644
index 0000000..287cb23
--- /dev/null
+++ b/src/tests/userprog/create-null.c
@@ -0,0 +1,11 @@
+/* Tries to create a file with the null pointer as its name.
+ The process must be terminated with exit code -1. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ msg ("create(NULL): %d", create (NULL, 0));
+}
diff --git a/src/tests/userprog/create-null.ck b/src/tests/userprog/create-null.ck
new file mode 100644
index 0000000..09b7872
--- /dev/null
+++ b/src/tests/userprog/create-null.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(create-null) begin
+create-null: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/exec-arg.c b/src/tests/userprog/exec-arg.c
new file mode 100644
index 0000000..82d0744
--- /dev/null
+++ b/src/tests/userprog/exec-arg.c
@@ -0,0 +1,10 @@
+/* Tests argument passing to child processes. */
+
+#include <syscall.h>
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ wait (exec ("child-args childarg"));
+}
diff --git a/src/tests/userprog/exec-arg.ck b/src/tests/userprog/exec-arg.ck
new file mode 100644
index 0000000..b7533ed
--- /dev/null
+++ b/src/tests/userprog/exec-arg.ck
@@ -0,0 +1,17 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(exec-arg) begin
+(args) begin
+(args) argc = 2
+(args) argv[0] = 'child-args'
+(args) argv[1] = 'childarg'
+(args) argv[2] = null
+(args) end
+child-args: exit(0)
+(exec-arg) end
+exec-arg: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/exec-bad-ptr.c b/src/tests/userprog/exec-bad-ptr.c
new file mode 100644
index 0000000..0abadd3
--- /dev/null
+++ b/src/tests/userprog/exec-bad-ptr.c
@@ -0,0 +1,11 @@
+/* Passes an invalid pointer to the exec system call.
+ The process must be terminated with -1 exit code. */
+
+#include <syscall.h>
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ exec ((char *) 0x20101234);
+}
diff --git a/src/tests/userprog/exec-bad-ptr.ck b/src/tests/userprog/exec-bad-ptr.ck
new file mode 100644
index 0000000..63f5f78
--- /dev/null
+++ b/src/tests/userprog/exec-bad-ptr.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(exec-bad-ptr) begin
+(exec-bad-ptr) end
+exec-bad-ptr: exit(0)
+EOF
+(exec-bad-ptr) begin
+exec-bad-ptr: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/exec-missing.c b/src/tests/userprog/exec-missing.c
new file mode 100644
index 0000000..bf08cad
--- /dev/null
+++ b/src/tests/userprog/exec-missing.c
@@ -0,0 +1,12 @@
+/* Tries to execute a nonexistent process.
+ The exec system call must return -1. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ msg ("exec(\"no-such-file\"): %d", exec ("no-such-file"));
+}
diff --git a/src/tests/userprog/exec-missing.ck b/src/tests/userprog/exec-missing.ck
new file mode 100644
index 0000000..0ef7aaa
--- /dev/null
+++ b/src/tests/userprog/exec-missing.ck
@@ -0,0 +1,31 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF', <<'EOF', <<'EOF']);
+(exec-missing) begin
+load: no-such-file: open failed
+(exec-missing) exec("no-such-file"): -1
+(exec-missing) end
+exec-missing: exit(0)
+EOF
+(exec-missing) begin
+(exec-missing) exec("no-such-file"): -1
+(exec-missing) end
+exec-missing: exit(0)
+EOF
+(exec-missing) begin
+load: no-such-file: open failed
+no-such-file: exit(-1)
+(exec-missing) exec("no-such-file"): -1
+(exec-missing) end
+exec-missing: exit(0)
+EOF
+(exec-missing) begin
+load: no-such-file: open failed
+(exec-missing) exec("no-such-file"): -1
+no-such-file: exit(-1)
+(exec-missing) end
+exec-missing: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/exec-multiple.c b/src/tests/userprog/exec-multiple.c
new file mode 100644
index 0000000..ba4c26e
--- /dev/null
+++ b/src/tests/userprog/exec-multiple.c
@@ -0,0 +1,14 @@
+/* Executes and waits for multiple child processes. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ wait (exec ("child-simple"));
+ wait (exec ("child-simple"));
+ wait (exec ("child-simple"));
+ wait (exec ("child-simple"));
+}
diff --git a/src/tests/userprog/exec-multiple.ck b/src/tests/userprog/exec-multiple.ck
new file mode 100644
index 0000000..99624cd
--- /dev/null
+++ b/src/tests/userprog/exec-multiple.ck
@@ -0,0 +1,18 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(exec-multiple) begin
+(child-simple) run
+child-simple: exit(81)
+(child-simple) run
+child-simple: exit(81)
+(child-simple) run
+child-simple: exit(81)
+(child-simple) run
+child-simple: exit(81)
+(exec-multiple) end
+exec-multiple: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/exec-once.c b/src/tests/userprog/exec-once.c
new file mode 100644
index 0000000..7bae5a1
--- /dev/null
+++ b/src/tests/userprog/exec-once.c
@@ -0,0 +1,11 @@
+/* Executes and waits for a single child process. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ wait (exec ("child-simple"));
+}
diff --git a/src/tests/userprog/exec-once.ck b/src/tests/userprog/exec-once.ck
new file mode 100644
index 0000000..00b59ed
--- /dev/null
+++ b/src/tests/userprog/exec-once.ck
@@ -0,0 +1,12 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(exec-once) begin
+(child-simple) run
+child-simple: exit(81)
+(exec-once) end
+exec-once: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/exit.c b/src/tests/userprog/exit.c
new file mode 100644
index 0000000..cb4eb8f
--- /dev/null
+++ b/src/tests/userprog/exit.c
@@ -0,0 +1,11 @@
+/* Tests the exit system call. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ exit (57);
+ fail ("should have called exit(57)");
+}
diff --git a/src/tests/userprog/exit.ck b/src/tests/userprog/exit.ck
new file mode 100644
index 0000000..a552702
--- /dev/null
+++ b/src/tests/userprog/exit.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(exit) begin
+exit: exit(57)
+EOF
+pass;
diff --git a/src/tests/userprog/halt.c b/src/tests/userprog/halt.c
new file mode 100644
index 0000000..4a99bce
--- /dev/null
+++ b/src/tests/userprog/halt.c
@@ -0,0 +1,11 @@
+/* Tests the halt system call. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ halt ();
+ fail ("should have halted");
+}
diff --git a/src/tests/userprog/halt.ck b/src/tests/userprog/halt.ck
new file mode 100644
index 0000000..1b701ed
--- /dev/null
+++ b/src/tests/userprog/halt.ck
@@ -0,0 +1,15 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+
+our ($test);
+my (@output) = read_text_file ("$test.output");
+
+common_checks ("run", @output);
+
+fail "missing 'begin' message\n"
+ if !grep ($_ eq '(halt) begin', @output);
+fail "found 'fail' message--halt didn't really halt\n"
+ if grep ($_ eq '(halt) fail', @output);
+pass;
diff --git a/src/tests/userprog/lib/.cvsignore b/src/tests/userprog/lib/.cvsignore
new file mode 100644
index 0000000..a438335
--- /dev/null
+++ b/src/tests/userprog/lib/.cvsignore
@@ -0,0 +1 @@
+*.d
diff --git a/src/tests/userprog/lib/user/.cvsignore b/src/tests/userprog/lib/user/.cvsignore
new file mode 100644
index 0000000..a438335
--- /dev/null
+++ b/src/tests/userprog/lib/user/.cvsignore
@@ -0,0 +1 @@
+*.d
diff --git a/src/tests/userprog/lib/user/.dummy b/src/tests/userprog/lib/user/.dummy
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/tests/userprog/lib/user/.dummy
diff --git a/src/tests/userprog/multi-child-fd.c b/src/tests/userprog/multi-child-fd.c
new file mode 100644
index 0000000..48de4b4
--- /dev/null
+++ b/src/tests/userprog/multi-child-fd.c
@@ -0,0 +1,25 @@
+/* Opens a file and then runs a subprocess that tries to close
+ the file. (Pintos does not have inheritance of file handles,
+ so this must fail.) The parent process then attempts to use
+ the file handle, which must succeed. */
+
+#include <stdio.h>
+#include <syscall.h>
+#include "tests/userprog/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ char child_cmd[128];
+ int handle;
+
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+
+ snprintf (child_cmd, sizeof child_cmd, "child-close %d", handle);
+
+ msg ("wait(exec()) = %d", wait (exec (child_cmd)));
+
+ check_file_handle (handle, "sample.txt", sample, sizeof sample - 1);
+}
diff --git a/src/tests/userprog/multi-child-fd.ck b/src/tests/userprog/multi-child-fd.ck
new file mode 100644
index 0000000..d0b3a33
--- /dev/null
+++ b/src/tests/userprog/multi-child-fd.ck
@@ -0,0 +1,25 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(multi-child-fd) begin
+(multi-child-fd) open "sample.txt"
+(child-close) begin
+(child-close) end
+child-close: exit(0)
+(multi-child-fd) wait(exec()) = 0
+(multi-child-fd) verified contents of "sample.txt"
+(multi-child-fd) end
+multi-child-fd: exit(0)
+EOF
+(multi-child-fd) begin
+(multi-child-fd) open "sample.txt"
+(child-close) begin
+child-close: exit(-1)
+(multi-child-fd) wait(exec()) = -1
+(multi-child-fd) verified contents of "sample.txt"
+(multi-child-fd) end
+multi-child-fd: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/multi-recurse.c b/src/tests/userprog/multi-recurse.c
new file mode 100644
index 0000000..7172ec3
--- /dev/null
+++ b/src/tests/userprog/multi-recurse.c
@@ -0,0 +1,34 @@
+/* Executes itself recursively to the depth indicated by the
+ first command-line argument. */
+
+#include <debug.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <syscall.h>
+#include "tests/lib.h"
+
+const char *test_name = "multi-recurse";
+
+int
+main (int argc UNUSED, char *argv[])
+{
+ int n = atoi (argv[1]);
+
+ msg ("begin %d", n);
+ if (n != 0)
+ {
+ char child_cmd[128];
+ pid_t child_pid;
+ int code;
+
+ snprintf (child_cmd, sizeof child_cmd, "multi-recurse %d", n - 1);
+ CHECK ((child_pid = exec (child_cmd)) != -1, "exec(\"%s\")", child_cmd);
+
+ code = wait (child_pid);
+ if (code != n - 1)
+ fail ("wait(exec(\"%s\")) returned %d", child_cmd, code);
+ }
+
+ msg ("end %d", n);
+ return n;
+}
diff --git a/src/tests/userprog/multi-recurse.ck b/src/tests/userprog/multi-recurse.ck
new file mode 100644
index 0000000..41eb4a6
--- /dev/null
+++ b/src/tests/userprog/multi-recurse.ck
@@ -0,0 +1,70 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(multi-recurse) begin 15
+(multi-recurse) exec("multi-recurse 14")
+(multi-recurse) begin 14
+(multi-recurse) exec("multi-recurse 13")
+(multi-recurse) begin 13
+(multi-recurse) exec("multi-recurse 12")
+(multi-recurse) begin 12
+(multi-recurse) exec("multi-recurse 11")
+(multi-recurse) begin 11
+(multi-recurse) exec("multi-recurse 10")
+(multi-recurse) begin 10
+(multi-recurse) exec("multi-recurse 9")
+(multi-recurse) begin 9
+(multi-recurse) exec("multi-recurse 8")
+(multi-recurse) begin 8
+(multi-recurse) exec("multi-recurse 7")
+(multi-recurse) begin 7
+(multi-recurse) exec("multi-recurse 6")
+(multi-recurse) begin 6
+(multi-recurse) exec("multi-recurse 5")
+(multi-recurse) begin 5
+(multi-recurse) exec("multi-recurse 4")
+(multi-recurse) begin 4
+(multi-recurse) exec("multi-recurse 3")
+(multi-recurse) begin 3
+(multi-recurse) exec("multi-recurse 2")
+(multi-recurse) begin 2
+(multi-recurse) exec("multi-recurse 1")
+(multi-recurse) begin 1
+(multi-recurse) exec("multi-recurse 0")
+(multi-recurse) begin 0
+(multi-recurse) end 0
+multi-recurse: exit(0)
+(multi-recurse) end 1
+multi-recurse: exit(1)
+(multi-recurse) end 2
+multi-recurse: exit(2)
+(multi-recurse) end 3
+multi-recurse: exit(3)
+(multi-recurse) end 4
+multi-recurse: exit(4)
+(multi-recurse) end 5
+multi-recurse: exit(5)
+(multi-recurse) end 6
+multi-recurse: exit(6)
+(multi-recurse) end 7
+multi-recurse: exit(7)
+(multi-recurse) end 8
+multi-recurse: exit(8)
+(multi-recurse) end 9
+multi-recurse: exit(9)
+(multi-recurse) end 10
+multi-recurse: exit(10)
+(multi-recurse) end 11
+multi-recurse: exit(11)
+(multi-recurse) end 12
+multi-recurse: exit(12)
+(multi-recurse) end 13
+multi-recurse: exit(13)
+(multi-recurse) end 14
+multi-recurse: exit(14)
+(multi-recurse) end 15
+multi-recurse: exit(15)
+EOF
+pass;
diff --git a/src/tests/userprog/no-vm/Make.tests b/src/tests/userprog/no-vm/Make.tests
new file mode 100644
index 0000000..a545e18
--- /dev/null
+++ b/src/tests/userprog/no-vm/Make.tests
@@ -0,0 +1,8 @@
+# -*- makefile -*-
+
+tests/userprog/no-vm_TESTS = tests/userprog/no-vm/multi-oom
+tests/userprog/no-vm_PROGS = $(tests/userprog/no-vm_TESTS)
+tests/userprog/no-vm/multi-oom_SRC = tests/userprog/no-vm/multi-oom.c \
+tests/lib.c
+
+tests/userprog/no-vm/multi-oom.output: TIMEOUT = 360
diff --git a/src/tests/userprog/no-vm/Rubric b/src/tests/userprog/no-vm/Rubric
new file mode 100644
index 0000000..c3816c6
--- /dev/null
+++ b/src/tests/userprog/no-vm/Rubric
@@ -0,0 +1,3 @@
+Functionality of features that VM might break:
+
+1 multi-oom
diff --git a/src/tests/userprog/no-vm/multi-oom.c b/src/tests/userprog/no-vm/multi-oom.c
new file mode 100644
index 0000000..6a4472d
--- /dev/null
+++ b/src/tests/userprog/no-vm/multi-oom.c
@@ -0,0 +1,179 @@
+/* Recursively executes itself until the child fails to execute.
+ We expect that at least 30 copies can run.
+
+ We count how many children your kernel was able to execute
+ before it fails to start a new process. We require that,
+ if a process doesn't actually get to start, exec() must
+ return -1, not a valid PID.
+
+ We repeat this process 10 times, checking that your kernel
+ allows for the same level of depth every time.
+
+ In addition, some processes will spawn children that terminate
+ abnormally after allocating some resources.
+
+ Written by Godmar Back <godmar@gmail.com>
+ */
+
+#include <debug.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <syscall.h>
+#include <random.h>
+#include "tests/lib.h"
+
+static const int EXPECTED_DEPTH_TO_PASS = 30;
+static const int EXPECTED_REPETITIONS = 10;
+
+const char *test_name = "multi-oom";
+
+enum child_termination_mode { RECURSE, CRASH };
+
+/* Spawn a recursive copy of ourselves, passing along instructions
+ for the child. */
+static pid_t
+spawn_child (int c, enum child_termination_mode mode)
+{
+ char child_cmd[128];
+ snprintf (child_cmd, sizeof child_cmd,
+ "%s %d %s", test_name, c, mode == CRASH ? "-k" : "");
+ return exec (child_cmd);
+}
+
+/* Open a number of files (and fail to close them).
+ The kernel must free any kernel resources associated
+ with these file descriptors. */
+static void
+consume_some_resources (void)
+{
+ int fd, fdmax = 126;
+
+ /* Open as many files as we can, up to fdmax.
+ Depending on how file descriptors are allocated inside
+ the kernel, open() may fail if the kernel is low on memory.
+ A low-memory condition in open() should not lead to the
+ termination of the process. */
+ for (fd = 0; fd < fdmax; fd++)
+ if (open (test_name) == -1)
+ break;
+}
+
+/* Consume some resources, then terminate this process
+ in some abnormal way. */
+static int NO_INLINE
+consume_some_resources_and_die (int seed)
+{
+ consume_some_resources ();
+ random_init (seed);
+ int *PHYS_BASE = (int *)0xC0000000;
+
+ switch (random_ulong () % 5)
+ {
+ case 0:
+ *(int *) NULL = 42;
+
+ case 1:
+ return *(int *) NULL;
+
+ case 2:
+ return *PHYS_BASE;
+
+ case 3:
+ *PHYS_BASE = 42;
+
+ case 4:
+ open ((char *)PHYS_BASE);
+ exit (-1);
+
+ default:
+ NOT_REACHED ();
+ }
+ return 0;
+}
+
+/* The first copy is invoked without command line arguments.
+ Subsequent copies are invoked with a parameter 'depth'
+ that describes how many parent processes preceded them.
+ Each process spawns one or multiple recursive copies of
+ itself, passing 'depth+1' as depth.
+
+ Some children are started with the '-k' flag, which will
+ result in abnormal termination.
+ */
+int
+main (int argc, char *argv[])
+{
+ int n;
+
+ n = argc > 1 ? atoi (argv[1]) : 0;
+ bool is_at_root = (n == 0);
+ if (is_at_root)
+ msg ("begin");
+
+ /* If -k is passed, crash this process. */
+ if (argc > 2 && !strcmp(argv[2], "-k"))
+ {
+ consume_some_resources_and_die (n);
+ NOT_REACHED ();
+ }
+
+ int howmany = is_at_root ? EXPECTED_REPETITIONS : 1;
+ int i, expected_depth = -1;
+
+ for (i = 0; i < howmany; i++)
+ {
+ pid_t child_pid;
+
+ /* Spawn a child that will be abnormally terminated.
+ To speed the test up, do this only for processes
+ spawned at a certain depth. */
+ if (n > EXPECTED_DEPTH_TO_PASS/2)
+ {
+ child_pid = spawn_child (n + 1, CRASH);
+ if (child_pid != -1)
+ {
+ if (wait (child_pid) != -1)
+ fail ("crashed child should return -1.");
+ }
+ /* If spawning this child failed, so should
+ the next spawn_child below. */
+ }
+
+ /* Now spawn the child that will recurse. */
+ child_pid = spawn_child (n + 1, RECURSE);
+
+ /* If maximum depth is reached, return result. */
+ if (child_pid == -1)
+ return n;
+
+ /* Else wait for child to report how deeply it was able to recurse. */
+ int reached_depth = wait (child_pid);
+ if (reached_depth == -1)
+ fail ("wait returned -1.");
+
+ /* Record the depth reached during the first run; on subsequent
+ runs, fail if those runs do not match the depth achieved on the
+ first run. */
+ if (i == 0)
+ expected_depth = reached_depth;
+ else if (expected_depth != reached_depth)
+ fail ("after run %d/%d, expected depth %d, actual depth %d.",
+ i, howmany, expected_depth, reached_depth);
+ ASSERT (expected_depth == reached_depth);
+ }
+
+ consume_some_resources ();
+
+ if (n == 0)
+ {
+ if (expected_depth < EXPECTED_DEPTH_TO_PASS)
+ fail ("should have forked at least %d times.", EXPECTED_DEPTH_TO_PASS);
+ msg ("success. program forked %d times.", howmany);
+ msg ("end");
+ }
+
+ return expected_depth;
+}
+// vim: sw=2
diff --git a/src/tests/userprog/no-vm/multi-oom.ck b/src/tests/userprog/no-vm/multi-oom.ck
new file mode 100644
index 0000000..59a0bcd
--- /dev/null
+++ b/src/tests/userprog/no-vm/multi-oom.ck
@@ -0,0 +1,10 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_USER_FAULTS => 1, IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(multi-oom) begin
+(multi-oom) success. program forked 10 times.
+(multi-oom) end
+EOF
+pass;
diff --git a/src/tests/userprog/null.ck b/src/tests/userprog/null.ck
new file mode 100644
index 0000000..980de35
--- /dev/null
+++ b/src/tests/userprog/null.ck
@@ -0,0 +1,8 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+system call!
+EOF
+pass;
diff --git a/src/tests/userprog/open-bad-ptr.c b/src/tests/userprog/open-bad-ptr.c
new file mode 100644
index 0000000..9cd4edf
--- /dev/null
+++ b/src/tests/userprog/open-bad-ptr.c
@@ -0,0 +1,13 @@
+/* Passes an invalid pointer to the open system call.
+ The process must be terminated with -1 exit code. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ msg ("open(0x20101234): %d", open ((char *) 0x20101234));
+ fail ("should have called exit(-1)");
+}
diff --git a/src/tests/userprog/open-bad-ptr.ck b/src/tests/userprog/open-bad-ptr.ck
new file mode 100644
index 0000000..45349e2
--- /dev/null
+++ b/src/tests/userprog/open-bad-ptr.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(open-bad-ptr) begin
+(open-bad-ptr) end
+open-bad-ptr: exit(0)
+EOF
+(open-bad-ptr) begin
+open-bad-ptr: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/open-boundary.c b/src/tests/userprog/open-boundary.c
new file mode 100644
index 0000000..cc8ff8b
--- /dev/null
+++ b/src/tests/userprog/open-boundary.c
@@ -0,0 +1,14 @@
+/* Creates a file whose name spans the boundary between two pages.
+ This is valid, so it must succeed. */
+
+#include <syscall.h>
+#include "tests/userprog/boundary.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ CHECK (open (copy_string_across_boundary ("sample.txt")) > 1,
+ "open \"sample.txt\"");
+}
diff --git a/src/tests/userprog/open-boundary.ck b/src/tests/userprog/open-boundary.ck
new file mode 100644
index 0000000..8060d22
--- /dev/null
+++ b/src/tests/userprog/open-boundary.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(open-boundary) begin
+(open-boundary) open "sample.txt"
+(open-boundary) end
+open-boundary: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/open-empty.c b/src/tests/userprog/open-empty.c
new file mode 100644
index 0000000..3ea9907
--- /dev/null
+++ b/src/tests/userprog/open-empty.c
@@ -0,0 +1,13 @@
+/* Tries to open a file with the empty string as its name. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ int handle = open ("");
+ if (handle != -1)
+ fail ("open() returned %d instead of -1", handle);
+}
diff --git a/src/tests/userprog/open-empty.ck b/src/tests/userprog/open-empty.ck
new file mode 100644
index 0000000..885fb41
--- /dev/null
+++ b/src/tests/userprog/open-empty.ck
@@ -0,0 +1,10 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(open-empty) begin
+(open-empty) end
+open-empty: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/open-missing.c b/src/tests/userprog/open-missing.c
new file mode 100644
index 0000000..13ecbda
--- /dev/null
+++ b/src/tests/userprog/open-missing.c
@@ -0,0 +1,13 @@
+/* Tries to open a nonexistent file. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ int handle = open ("no-such-file");
+ if (handle != -1)
+ fail ("open() returned %d", handle);
+}
diff --git a/src/tests/userprog/open-missing.ck b/src/tests/userprog/open-missing.ck
new file mode 100644
index 0000000..d72d878
--- /dev/null
+++ b/src/tests/userprog/open-missing.ck
@@ -0,0 +1,10 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(open-missing) begin
+(open-missing) end
+open-missing: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/open-normal.c b/src/tests/userprog/open-normal.c
new file mode 100644
index 0000000..5132465
--- /dev/null
+++ b/src/tests/userprog/open-normal.c
@@ -0,0 +1,13 @@
+/* Open a file. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ int handle = open ("sample.txt");
+ if (handle < 2)
+ fail ("open() returned %d", handle);
+}
diff --git a/src/tests/userprog/open-normal.ck b/src/tests/userprog/open-normal.ck
new file mode 100644
index 0000000..4f6c342
--- /dev/null
+++ b/src/tests/userprog/open-normal.ck
@@ -0,0 +1,10 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(open-normal) begin
+(open-normal) end
+open-normal: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/open-null.c b/src/tests/userprog/open-null.c
new file mode 100644
index 0000000..bb418b8
--- /dev/null
+++ b/src/tests/userprog/open-null.c
@@ -0,0 +1,12 @@
+/* Tries to open a file with the null pointer as its name.
+ The process must be terminated with exit code -1. */
+
+#include <stddef.h>
+#include <syscall.h>
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ open (NULL);
+}
diff --git a/src/tests/userprog/open-null.ck b/src/tests/userprog/open-null.ck
new file mode 100644
index 0000000..b4a3bcb
--- /dev/null
+++ b/src/tests/userprog/open-null.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(open-null) begin
+(open-null) end
+open-null: exit(0)
+EOF
+(open-null) begin
+open-null: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/open-twice.c b/src/tests/userprog/open-twice.c
new file mode 100644
index 0000000..dd333af
--- /dev/null
+++ b/src/tests/userprog/open-twice.c
@@ -0,0 +1,19 @@
+/* Tries to open the same file twice,
+ which must succeed and must return a different file descriptor
+ in each case. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ int h1 = open ("sample.txt");
+ int h2 = open ("sample.txt");
+
+ CHECK ((h1 = open ("sample.txt")) > 1, "open \"sample.txt\" once");
+ CHECK ((h2 = open ("sample.txt")) > 1, "open \"sample.txt\" again");
+ if (h1 == h2)
+ fail ("open() returned %d both times", h1);
+}
diff --git a/src/tests/userprog/open-twice.ck b/src/tests/userprog/open-twice.ck
new file mode 100644
index 0000000..64fa805
--- /dev/null
+++ b/src/tests/userprog/open-twice.ck
@@ -0,0 +1,12 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(open-twice) begin
+(open-twice) open "sample.txt" once
+(open-twice) open "sample.txt" again
+(open-twice) end
+open-twice: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/read-bad-fd.c b/src/tests/userprog/read-bad-fd.c
new file mode 100644
index 0000000..a8b190d
--- /dev/null
+++ b/src/tests/userprog/read-bad-fd.c
@@ -0,0 +1,21 @@
+/* Tries to read from an invalid fd,
+ which must either fail silently or terminate the process with
+ exit code -1. */
+
+#include <limits.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ char buf;
+ read (0x20101234, &buf, 1);
+ read (5, &buf, 1);
+ read (1234, &buf, 1);
+ read (-1, &buf, 1);
+ read (-1024, &buf, 1);
+ read (INT_MIN, &buf, 1);
+ read (INT_MAX, &buf, 1);
+}
diff --git a/src/tests/userprog/read-bad-fd.ck b/src/tests/userprog/read-bad-fd.ck
new file mode 100644
index 0000000..5fedcc7
--- /dev/null
+++ b/src/tests/userprog/read-bad-fd.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(read-bad-fd) begin
+(read-bad-fd) end
+read-bad-fd: exit(0)
+EOF
+(read-bad-fd) begin
+read-bad-fd: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/read-bad-ptr.c b/src/tests/userprog/read-bad-ptr.c
new file mode 100644
index 0000000..8fe756e
--- /dev/null
+++ b/src/tests/userprog/read-bad-ptr.c
@@ -0,0 +1,16 @@
+/* Passes an invalid pointer to the read system call.
+ The process must be terminated with -1 exit code. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ int handle;
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+
+ read (handle, (char *) 0xc0100000, 123);
+ fail ("should not have survived read()");
+}
diff --git a/src/tests/userprog/read-bad-ptr.ck b/src/tests/userprog/read-bad-ptr.ck
new file mode 100644
index 0000000..d10accf
--- /dev/null
+++ b/src/tests/userprog/read-bad-ptr.ck
@@ -0,0 +1,15 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(read-bad-ptr) begin
+(read-bad-ptr) open "sample.txt"
+(read-bad-ptr) end
+read-bad-ptr: exit(0)
+EOF
+(read-bad-ptr) begin
+(read-bad-ptr) open "sample.txt"
+read-bad-ptr: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/read-boundary.c b/src/tests/userprog/read-boundary.c
new file mode 100644
index 0000000..9c19966
--- /dev/null
+++ b/src/tests/userprog/read-boundary.c
@@ -0,0 +1,30 @@
+/* Reads data spanning two pages in virtual address space,
+ which must succeed. */
+
+#include <string.h>
+#include <syscall.h>
+#include "tests/userprog/boundary.h"
+#include "tests/userprog/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ int handle;
+ int byte_cnt;
+ char *buffer;
+
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+
+ buffer = get_boundary_area () - sizeof sample / 2;
+ byte_cnt = read (handle, buffer, sizeof sample - 1);
+ if (byte_cnt != sizeof sample - 1)
+ fail ("read() returned %d instead of %zu", byte_cnt, sizeof sample - 1);
+ else if (strcmp (sample, buffer))
+ {
+ msg ("expected text:\n%s", sample);
+ msg ("text actually read:\n%s", buffer);
+ fail ("expected text differs from actual");
+ }
+}
diff --git a/src/tests/userprog/read-boundary.ck b/src/tests/userprog/read-boundary.ck
new file mode 100644
index 0000000..08dc161
--- /dev/null
+++ b/src/tests/userprog/read-boundary.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(read-boundary) begin
+(read-boundary) open "sample.txt"
+(read-boundary) end
+read-boundary: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/read-normal.c b/src/tests/userprog/read-normal.c
new file mode 100644
index 0000000..16d15cc
--- /dev/null
+++ b/src/tests/userprog/read-normal.c
@@ -0,0 +1,11 @@
+/* Try reading a file in the most normal way. */
+
+#include "tests/userprog/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ check_file ("sample.txt", sample, sizeof sample - 1);
+}
diff --git a/src/tests/userprog/read-normal.ck b/src/tests/userprog/read-normal.ck
new file mode 100644
index 0000000..0ed2998
--- /dev/null
+++ b/src/tests/userprog/read-normal.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(read-normal) begin
+(read-normal) open "sample.txt" for verification
+(read-normal) verified contents of "sample.txt"
+(read-normal) close "sample.txt"
+(read-normal) end
+read-normal: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/read-stdout.c b/src/tests/userprog/read-stdout.c
new file mode 100644
index 0000000..d0630b9
--- /dev/null
+++ b/src/tests/userprog/read-stdout.c
@@ -0,0 +1,14 @@
+/* Try reading from fd 1 (stdout),
+ which may just fail or terminate the process with -1 exit
+ code. */
+
+#include <stdio.h>
+#include <syscall.h>
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ char buf;
+ read (STDOUT_FILENO, &buf, 1);
+}
diff --git a/src/tests/userprog/read-stdout.ck b/src/tests/userprog/read-stdout.ck
new file mode 100644
index 0000000..7d87b52
--- /dev/null
+++ b/src/tests/userprog/read-stdout.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(read-stdout) begin
+(read-stdout) end
+read-stdout: exit(0)
+EOF
+(read-stdout) begin
+read-stdout: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/read-zero.c b/src/tests/userprog/read-zero.c
new file mode 100644
index 0000000..e441817
--- /dev/null
+++ b/src/tests/userprog/read-zero.c
@@ -0,0 +1,22 @@
+/* Try a 0-byte read, which should return 0 without reading
+ anything. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ int handle, byte_cnt;
+ char buf;
+
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+
+ buf = 123;
+ byte_cnt = read (handle, &buf, 0);
+ if (byte_cnt != 0)
+ fail ("read() returned %d instead of 0", byte_cnt);
+ else if (buf != 123)
+ fail ("0-byte read() modified buffer");
+}
diff --git a/src/tests/userprog/read-zero.ck b/src/tests/userprog/read-zero.ck
new file mode 100644
index 0000000..8346dbc
--- /dev/null
+++ b/src/tests/userprog/read-zero.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(read-zero) begin
+(read-zero) open "sample.txt"
+(read-zero) end
+read-zero: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/rox-child.c b/src/tests/userprog/rox-child.c
new file mode 100644
index 0000000..30afba2
--- /dev/null
+++ b/src/tests/userprog/rox-child.c
@@ -0,0 +1,5 @@
+/* Ensure that the executable of a running process cannot be
+ modified, even by a child process. */
+
+#define CHILD_CNT "1"
+#include "tests/userprog/rox-child.inc"
diff --git a/src/tests/userprog/rox-child.ck b/src/tests/userprog/rox-child.ck
new file mode 100644
index 0000000..e6363fb
--- /dev/null
+++ b/src/tests/userprog/rox-child.ck
@@ -0,0 +1,20 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(rox-child) begin
+(rox-child) open "child-rox"
+(rox-child) read "child-rox"
+(rox-child) write "child-rox"
+(rox-child) exec "child-rox 1"
+(child-rox) begin
+(child-rox) try to write "child-rox"
+(child-rox) try to write "child-rox"
+(child-rox) end
+child-rox: exit(12)
+(rox-child) write "child-rox"
+(rox-child) end
+rox-child: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/rox-child.inc b/src/tests/userprog/rox-child.inc
new file mode 100644
index 0000000..1e2ade9
--- /dev/null
+++ b/src/tests/userprog/rox-child.inc
@@ -0,0 +1,33 @@
+/* -*- c -*- */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ const char *child_cmd = "child-rox " CHILD_CNT;
+ int handle;
+ pid_t child;
+ char buffer[16];
+
+ /* Open child-rox, read from it, write back same data. */
+ CHECK ((handle = open ("child-rox")) > 1, "open \"child-rox\"");
+ CHECK (read (handle, buffer, sizeof buffer) == (int) sizeof buffer,
+ "read \"child-rox\"");
+ seek (handle, 0);
+ CHECK (write (handle, buffer, sizeof buffer) == (int) sizeof buffer,
+ "write \"child-rox\"");
+
+ /* Execute child-rox and wait for it. */
+ CHECK ((child = exec (child_cmd)) != -1, "exec \"%s\"", child_cmd);
+ quiet = true;
+ CHECK (wait (child) == 12, "wait for child");
+ quiet = false;
+
+ /* Write to child-rox again. */
+ seek (handle, 0);
+ CHECK (write (handle, buffer, sizeof buffer) == (int) sizeof buffer,
+ "write \"child-rox\"");
+}
diff --git a/src/tests/userprog/rox-multichild.c b/src/tests/userprog/rox-multichild.c
new file mode 100644
index 0000000..8e74dab
--- /dev/null
+++ b/src/tests/userprog/rox-multichild.c
@@ -0,0 +1,5 @@
+/* Ensure that the executable of a running process cannot be
+ modified, even in the presence of multiple children. */
+
+#define CHILD_CNT "5"
+#include "tests/userprog/rox-child.inc"
diff --git a/src/tests/userprog/rox-multichild.ck b/src/tests/userprog/rox-multichild.ck
new file mode 100644
index 0000000..14b27db
--- /dev/null
+++ b/src/tests/userprog/rox-multichild.ck
@@ -0,0 +1,44 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(rox-multichild) begin
+(rox-multichild) open "child-rox"
+(rox-multichild) read "child-rox"
+(rox-multichild) write "child-rox"
+(rox-multichild) exec "child-rox 5"
+(child-rox) begin
+(child-rox) try to write "child-rox"
+(child-rox) exec "child-rox 4"
+(child-rox) begin
+(child-rox) try to write "child-rox"
+(child-rox) exec "child-rox 3"
+(child-rox) begin
+(child-rox) try to write "child-rox"
+(child-rox) exec "child-rox 2"
+(child-rox) begin
+(child-rox) try to write "child-rox"
+(child-rox) exec "child-rox 1"
+(child-rox) begin
+(child-rox) try to write "child-rox"
+(child-rox) try to write "child-rox"
+(child-rox) end
+child-rox: exit(12)
+(child-rox) try to write "child-rox"
+(child-rox) end
+child-rox: exit(12)
+(child-rox) try to write "child-rox"
+(child-rox) end
+child-rox: exit(12)
+(child-rox) try to write "child-rox"
+(child-rox) end
+child-rox: exit(12)
+(child-rox) try to write "child-rox"
+(child-rox) end
+child-rox: exit(12)
+(rox-multichild) write "child-rox"
+(rox-multichild) end
+rox-multichild: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/rox-simple.c b/src/tests/userprog/rox-simple.c
new file mode 100644
index 0000000..e84a064
--- /dev/null
+++ b/src/tests/userprog/rox-simple.c
@@ -0,0 +1,19 @@
+/* Ensure that the executable of a running process cannot be
+ modified. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ int handle;
+ char buffer[16];
+
+ CHECK ((handle = open ("rox-simple")) > 1, "open \"rox-simple\"");
+ CHECK (read (handle, buffer, sizeof buffer) == (int) sizeof buffer,
+ "read \"rox-simple\"");
+ CHECK (write (handle, buffer, sizeof buffer) == 0,
+ "try to write \"rox-simple\"");
+}
diff --git a/src/tests/userprog/rox-simple.ck b/src/tests/userprog/rox-simple.ck
new file mode 100644
index 0000000..c9dcc66
--- /dev/null
+++ b/src/tests/userprog/rox-simple.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(rox-simple) begin
+(rox-simple) open "rox-simple"
+(rox-simple) read "rox-simple"
+(rox-simple) try to write "rox-simple"
+(rox-simple) end
+rox-simple: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/sample.inc b/src/tests/userprog/sample.inc
new file mode 100644
index 0000000..59f2bcb
--- /dev/null
+++ b/src/tests/userprog/sample.inc
@@ -0,0 +1,6 @@
+char sample[] = {
+ "\"Amazing Electronic Fact: If you scuffed your feet long enough without\n"
+ " touching anything, you would build up so many electrons that your\n"
+ " finger would explode! But this is nothing to worry about unless you\n"
+ " have carpeting.\" --Dave Barry\n"
+};
diff --git a/src/tests/userprog/sample.txt b/src/tests/userprog/sample.txt
new file mode 100644
index 0000000..5050fec
--- /dev/null
+++ b/src/tests/userprog/sample.txt
@@ -0,0 +1,4 @@
+"Amazing Electronic Fact: If you scuffed your feet long enough without
+ touching anything, you would build up so many electrons that your
+ finger would explode! But this is nothing to worry about unless you
+ have carpeting." --Dave Barry
diff --git a/src/tests/userprog/sc-bad-arg.c b/src/tests/userprog/sc-bad-arg.c
new file mode 100644
index 0000000..0b512a0
--- /dev/null
+++ b/src/tests/userprog/sc-bad-arg.c
@@ -0,0 +1,17 @@
+/* Sticks a system call number (SYS_EXIT) at the very top of the
+ stack, then invokes a system call with the stack pointer
+ (%esp) set to its address. The process must be terminated
+ with -1 exit code because the argument to the system call
+ would be above the top of the user address space. */
+
+#include <syscall-nr.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ asm volatile ("movl $0xbffffffc, %%esp; movl %0, (%%esp); int $0x30"
+ : : "i" (SYS_EXIT));
+ fail ("should have called exit(-1)");
+}
diff --git a/src/tests/userprog/sc-bad-arg.ck b/src/tests/userprog/sc-bad-arg.ck
new file mode 100644
index 0000000..8981105
--- /dev/null
+++ b/src/tests/userprog/sc-bad-arg.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(sc-bad-arg) begin
+sc-bad-arg: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/sc-bad-sp.c b/src/tests/userprog/sc-bad-sp.c
new file mode 100644
index 0000000..39cce84
--- /dev/null
+++ b/src/tests/userprog/sc-bad-sp.c
@@ -0,0 +1,20 @@
+/* Invokes a system call with the stack pointer (%esp) set to a
+ bad address. The process must be terminated with -1 exit
+ code.
+
+ For Project 3: The bad address lies approximately 64MB below
+ the code segment, so there is no ambiguity that this attempt
+ must be rejected even after stack growth is implemented.
+ Moreover, a good stack growth heuristics should probably not
+ grow the stack for the purpose of reading the system call
+ number and arguments. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ asm volatile ("movl $.-(64*1024*1024), %esp; int $0x30");
+ fail ("should have called exit(-1)");
+}
diff --git a/src/tests/userprog/sc-bad-sp.ck b/src/tests/userprog/sc-bad-sp.ck
new file mode 100644
index 0000000..498cec1
--- /dev/null
+++ b/src/tests/userprog/sc-bad-sp.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(sc-bad-sp) begin
+sc-bad-sp: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/sc-boundary-2.c b/src/tests/userprog/sc-boundary-2.c
new file mode 100644
index 0000000..8acf036
--- /dev/null
+++ b/src/tests/userprog/sc-boundary-2.c
@@ -0,0 +1,22 @@
+/* Invokes a system call with one byte of the system call's
+ argument on a separate page from the rest of the bytes. This
+ must work. */
+
+#include <syscall-nr.h>
+#include "tests/userprog/boundary.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ /* Make one byte of a syscall argument hang over into a second
+ page. */
+ int *p = (int *) ((char *) get_boundary_area () - 7);
+ p[0] = SYS_EXIT;
+ p[1] = 67;
+
+ /* Invoke the system call. */
+ asm volatile ("movl %0, %%esp; int $0x30" : : "g" (p));
+ fail ("should have called exit(67)");
+}
diff --git a/src/tests/userprog/sc-boundary-2.ck b/src/tests/userprog/sc-boundary-2.ck
new file mode 100644
index 0000000..43766bf
--- /dev/null
+++ b/src/tests/userprog/sc-boundary-2.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(sc-boundary-2) begin
+sc-boundary-2: exit(67)
+EOF
+pass;
diff --git a/src/tests/userprog/sc-boundary.c b/src/tests/userprog/sc-boundary.c
new file mode 100644
index 0000000..d889535
--- /dev/null
+++ b/src/tests/userprog/sc-boundary.c
@@ -0,0 +1,22 @@
+/* Invokes a system call with the system call number and its
+ argument on separate pages. This must work. */
+
+#include <syscall-nr.h>
+#include "tests/userprog/boundary.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ /* Put a syscall number at the end of one page
+ and its argument at the beginning of another. */
+ int *p = get_boundary_area ();
+ p--;
+ p[0] = SYS_EXIT;
+ p[1] = 42;
+
+ /* Invoke the system call. */
+ asm volatile ("movl %0, %%esp; int $0x30" : : "g" (p));
+ fail ("should have called exit(42)");
+}
diff --git a/src/tests/userprog/sc-boundary.ck b/src/tests/userprog/sc-boundary.ck
new file mode 100644
index 0000000..3f7cbaf
--- /dev/null
+++ b/src/tests/userprog/sc-boundary.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(sc-boundary) begin
+sc-boundary: exit(42)
+EOF
+pass;
diff --git a/src/tests/userprog/wait-bad-pid.c b/src/tests/userprog/wait-bad-pid.c
new file mode 100644
index 0000000..3fe8ee4
--- /dev/null
+++ b/src/tests/userprog/wait-bad-pid.c
@@ -0,0 +1,11 @@
+/* Waits for an invalid pid. This may fail or terminate the
+ process with -1 exit code. */
+
+#include <syscall.h>
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ wait ((pid_t) 0x0c020301);
+}
diff --git a/src/tests/userprog/wait-bad-pid.ck b/src/tests/userprog/wait-bad-pid.ck
new file mode 100644
index 0000000..db63fb9
--- /dev/null
+++ b/src/tests/userprog/wait-bad-pid.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(wait-bad-pid) begin
+(wait-bad-pid) end
+wait-bad-pid: exit(0)
+EOF
+(wait-bad-pid) begin
+wait-bad-pid: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/wait-killed.c b/src/tests/userprog/wait-killed.c
new file mode 100644
index 0000000..6a2a6b5
--- /dev/null
+++ b/src/tests/userprog/wait-killed.c
@@ -0,0 +1,11 @@
+/* Wait for a process that will be killed for bad behavior. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ msg ("wait(exec()) = %d", wait (exec ("child-bad")));
+}
diff --git a/src/tests/userprog/wait-killed.ck b/src/tests/userprog/wait-killed.ck
new file mode 100644
index 0000000..5df0e9c
--- /dev/null
+++ b/src/tests/userprog/wait-killed.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(wait-killed) begin
+(child-bad) begin
+child-bad: exit(-1)
+(wait-killed) wait(exec()) = -1
+(wait-killed) end
+wait-killed: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/wait-simple.c b/src/tests/userprog/wait-simple.c
new file mode 100644
index 0000000..d3afcf3
--- /dev/null
+++ b/src/tests/userprog/wait-simple.c
@@ -0,0 +1,11 @@
+/* Wait for a subprocess to finish. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ msg ("wait(exec()) = %d", wait (exec ("child-simple")));
+}
diff --git a/src/tests/userprog/wait-simple.ck b/src/tests/userprog/wait-simple.ck
new file mode 100644
index 0000000..93dd577
--- /dev/null
+++ b/src/tests/userprog/wait-simple.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(wait-simple) begin
+(child-simple) run
+child-simple: exit(81)
+(wait-simple) wait(exec()) = 81
+(wait-simple) end
+wait-simple: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/wait-twice.c b/src/tests/userprog/wait-twice.c
new file mode 100644
index 0000000..785e684
--- /dev/null
+++ b/src/tests/userprog/wait-twice.c
@@ -0,0 +1,15 @@
+/* Wait for a subprocess to finish, twice.
+ The first call must wait in the usual way and return the exit code.
+ The second wait call must return -1 immediately. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ pid_t child = exec ("child-simple");
+ msg ("wait(exec()) = %d", wait (child));
+ msg ("wait(exec()) = %d", wait (child));
+}
diff --git a/src/tests/userprog/wait-twice.ck b/src/tests/userprog/wait-twice.ck
new file mode 100644
index 0000000..6d53843
--- /dev/null
+++ b/src/tests/userprog/wait-twice.ck
@@ -0,0 +1,14 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(wait-twice) begin
+(child-simple) run
+child-simple: exit(81)
+(wait-twice) wait(exec()) = 81
+(wait-twice) wait(exec()) = -1
+(wait-twice) end
+wait-twice: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/write-bad-fd.c b/src/tests/userprog/write-bad-fd.c
new file mode 100644
index 0000000..f3b1151
--- /dev/null
+++ b/src/tests/userprog/write-bad-fd.c
@@ -0,0 +1,20 @@
+/* Tries to write to an invalid fd,
+ which must either fail silently or terminate the process with
+ exit code -1. */
+
+#include <limits.h>
+#include <syscall.h>
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ char buf = 123;
+ write (0x01012342, &buf, 1);
+ write (7, &buf, 1);
+ write (2546, &buf, 1);
+ write (-5, &buf, 1);
+ write (-8192, &buf, 1);
+ write (INT_MIN + 1, &buf, 1);
+ write (INT_MAX - 1, &buf, 1);
+}
diff --git a/src/tests/userprog/write-bad-fd.ck b/src/tests/userprog/write-bad-fd.ck
new file mode 100644
index 0000000..8da7a8b
--- /dev/null
+++ b/src/tests/userprog/write-bad-fd.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(write-bad-fd) begin
+(write-bad-fd) end
+write-bad-fd: exit(0)
+EOF
+(write-bad-fd) begin
+write-bad-fd: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/write-bad-ptr.c b/src/tests/userprog/write-bad-ptr.c
new file mode 100644
index 0000000..5336479
--- /dev/null
+++ b/src/tests/userprog/write-bad-ptr.c
@@ -0,0 +1,16 @@
+/* Passes an invalid pointer to the write system call.
+ The process must be terminated with -1 exit code. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ int handle;
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+
+ write (handle, (char *) 0x10123420, 123);
+ fail ("should have exited with -1");
+}
diff --git a/src/tests/userprog/write-bad-ptr.ck b/src/tests/userprog/write-bad-ptr.ck
new file mode 100644
index 0000000..ad9f399
--- /dev/null
+++ b/src/tests/userprog/write-bad-ptr.ck
@@ -0,0 +1,15 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(write-bad-ptr) begin
+(write-bad-ptr) open "sample.txt"
+(write-bad-ptr) end
+write-bad-ptr: exit(0)
+EOF
+(write-bad-ptr) begin
+(write-bad-ptr) open "sample.txt"
+write-bad-ptr: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/write-boundary.c b/src/tests/userprog/write-boundary.c
new file mode 100644
index 0000000..d2de1d4
--- /dev/null
+++ b/src/tests/userprog/write-boundary.c
@@ -0,0 +1,25 @@
+/* Writes data spanning two pages in virtual address space,
+ which must succeed. */
+
+#include <string.h>
+#include <syscall.h>
+#include "tests/userprog/boundary.h"
+#include "tests/userprog/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ int handle;
+ int byte_cnt;
+ char *sample_p;
+
+ sample_p = copy_string_across_boundary (sample);
+
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+
+ byte_cnt = write (handle, sample_p, sizeof sample - 1);
+ if (byte_cnt != sizeof sample - 1)
+ fail ("write() returned %d instead of %zu", byte_cnt, sizeof sample - 1);
+}
diff --git a/src/tests/userprog/write-boundary.ck b/src/tests/userprog/write-boundary.ck
new file mode 100644
index 0000000..7883781
--- /dev/null
+++ b/src/tests/userprog/write-boundary.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(write-boundary) begin
+(write-boundary) open "sample.txt"
+(write-boundary) end
+write-boundary: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/write-normal.c b/src/tests/userprog/write-normal.c
new file mode 100644
index 0000000..e0297aa
--- /dev/null
+++ b/src/tests/userprog/write-normal.c
@@ -0,0 +1,20 @@
+/* Try writing a file in the most normal way. */
+
+#include <syscall.h>
+#include "tests/userprog/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ int handle, byte_cnt;
+
+ CHECK (create ("test.txt", sizeof sample - 1), "create \"test.txt\"");
+ CHECK ((handle = open ("test.txt")) > 1, "open \"test.txt\"");
+
+ byte_cnt = write (handle, sample, sizeof sample - 1);
+ if (byte_cnt != sizeof sample - 1)
+ fail ("write() returned %d instead of %zu", byte_cnt, sizeof sample - 1);
+}
+
diff --git a/src/tests/userprog/write-normal.ck b/src/tests/userprog/write-normal.ck
new file mode 100644
index 0000000..9fa6024
--- /dev/null
+++ b/src/tests/userprog/write-normal.ck
@@ -0,0 +1,12 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(write-normal) begin
+(write-normal) create "test.txt"
+(write-normal) open "test.txt"
+(write-normal) end
+write-normal: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/write-stdin.c b/src/tests/userprog/write-stdin.c
new file mode 100644
index 0000000..491ea53
--- /dev/null
+++ b/src/tests/userprog/write-stdin.c
@@ -0,0 +1,14 @@
+/* Try writing to fd 0 (stdin),
+ which may just fail or terminate the process with -1 exit
+ code. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ char buf = 123;
+ write (0, &buf, 1);
+}
diff --git a/src/tests/userprog/write-stdin.ck b/src/tests/userprog/write-stdin.ck
new file mode 100644
index 0000000..a6caf81
--- /dev/null
+++ b/src/tests/userprog/write-stdin.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(write-stdin) begin
+(write-stdin) end
+write-stdin: exit(0)
+EOF
+(write-stdin) begin
+write-stdin: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/write-zero.c b/src/tests/userprog/write-zero.c
new file mode 100644
index 0000000..d8dac9b
--- /dev/null
+++ b/src/tests/userprog/write-zero.c
@@ -0,0 +1,20 @@
+/* Try a 0-byte write, which should return 0 without writing
+ anything. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ int handle, byte_cnt;
+ char buf;
+
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+
+ buf = 123;
+ byte_cnt = write (handle, &buf, 0);
+ if (byte_cnt != 0)
+ fail("write() returned %d instead of 0", byte_cnt);
+}
diff --git a/src/tests/userprog/write-zero.ck b/src/tests/userprog/write-zero.ck
new file mode 100644
index 0000000..cc4cd60
--- /dev/null
+++ b/src/tests/userprog/write-zero.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(write-zero) begin
+(write-zero) open "sample.txt"
+(write-zero) end
+write-zero: exit(0)
+EOF
+pass;