aboutsummaryrefslogtreecommitdiffstats
path: root/src/tests
diff options
context:
space:
mode:
Diffstat (limited to 'src/tests')
-rw-r--r--src/tests/Algorithm/Diff.pm1713
-rw-r--r--src/tests/Make.tests98
-rw-r--r--src/tests/arc4.c53
-rw-r--r--src/tests/arc4.h17
-rw-r--r--src/tests/arc4.pm29
-rw-r--r--src/tests/cksum.c92
-rw-r--r--src/tests/cksum.h8
-rw-r--r--src/tests/cksum.pm87
-rw-r--r--src/tests/filesys/Grading.no-vm18
-rw-r--r--src/tests/filesys/Grading.with-vm22
-rw-r--r--src/tests/filesys/base/Make.tests18
-rw-r--r--src/tests/filesys/base/Rubric19
-rw-r--r--src/tests/filesys/base/child-syn-read.c44
-rw-r--r--src/tests/filesys/base/child-syn-wrt.c35
-rw-r--r--src/tests/filesys/base/full.inc20
-rw-r--r--src/tests/filesys/base/lg-create.c5
-rw-r--r--src/tests/filesys/base/lg-create.ck13
-rw-r--r--src/tests/filesys/base/lg-full.c6
-rw-r--r--src/tests/filesys/base/lg-full.ck16
-rw-r--r--src/tests/filesys/base/lg-random.c7
-rw-r--r--src/tests/filesys/base/lg-random.ck14
-rw-r--r--src/tests/filesys/base/lg-seq-block.c7
-rw-r--r--src/tests/filesys/base/lg-seq-block.ck16
-rw-r--r--src/tests/filesys/base/lg-seq-random.c6
-rw-r--r--src/tests/filesys/base/lg-seq-random.ck16
-rw-r--r--src/tests/filesys/base/random.inc59
-rw-r--r--src/tests/filesys/base/seq-block.inc20
-rw-r--r--src/tests/filesys/base/seq-random.inc22
-rw-r--r--src/tests/filesys/base/sm-create.c5
-rw-r--r--src/tests/filesys/base/sm-create.ck13
-rw-r--r--src/tests/filesys/base/sm-full.c6
-rw-r--r--src/tests/filesys/base/sm-full.ck16
-rw-r--r--src/tests/filesys/base/sm-random.c7
-rw-r--r--src/tests/filesys/base/sm-random.ck14
-rw-r--r--src/tests/filesys/base/sm-seq-block.c7
-rw-r--r--src/tests/filesys/base/sm-seq-block.ck16
-rw-r--r--src/tests/filesys/base/sm-seq-random.c6
-rw-r--r--src/tests/filesys/base/sm-seq-random.ck16
-rw-r--r--src/tests/filesys/base/syn-read.c31
-rw-r--r--src/tests/filesys/base/syn-read.ck33
-rw-r--r--src/tests/filesys/base/syn-read.h7
-rw-r--r--src/tests/filesys/base/syn-remove.c30
-rw-r--r--src/tests/filesys/base/syn-remove.ck16
-rw-r--r--src/tests/filesys/base/syn-write.c31
-rw-r--r--src/tests/filesys/base/syn-write.ck32
-rw-r--r--src/tests/filesys/base/syn-write.h9
-rw-r--r--src/tests/filesys/create.inc15
-rw-r--r--src/tests/filesys/extended/Make.tests61
-rw-r--r--src/tests/filesys/extended/Rubric.functionality26
-rw-r--r--src/tests/filesys/extended/Rubric.persistence24
-rw-r--r--src/tests/filesys/extended/Rubric.robustness9
-rw-r--r--src/tests/filesys/extended/child-syn-rw.c53
-rw-r--r--src/tests/filesys/extended/dir-empty-name-persistence.ck6
-rw-r--r--src/tests/filesys/extended/dir-empty-name.c12
-rw-r--r--src/tests/filesys/extended/dir-empty-name.ck10
-rw-r--r--src/tests/filesys/extended/dir-mk-tree-persistence.ck16
-rw-r--r--src/tests/filesys/extended/dir-mk-tree.c12
-rw-r--r--src/tests/filesys/extended/dir-mk-tree.ck12
-rw-r--r--src/tests/filesys/extended/dir-mkdir-persistence.ck6
-rw-r--r--src/tests/filesys/extended/dir-mkdir.c15
-rw-r--r--src/tests/filesys/extended/dir-mkdir.ck13
-rw-r--r--src/tests/filesys/extended/dir-open-persistence.ck6
-rw-r--r--src/tests/filesys/extended/dir-open.c21
-rw-r--r--src/tests/filesys/extended/dir-open.ck20
-rw-r--r--src/tests/filesys/extended/dir-over-file-persistence.ck6
-rw-r--r--src/tests/filesys/extended/dir-over-file.c13
-rw-r--r--src/tests/filesys/extended/dir-over-file.ck11
-rw-r--r--src/tests/filesys/extended/dir-rm-cwd-persistence.ck8
-rw-r--r--src/tests/filesys/extended/dir-rm-cwd.c75
-rw-r--r--src/tests/filesys/extended/dir-rm-cwd.ck51
-rw-r--r--src/tests/filesys/extended/dir-rm-parent-persistence.ck6
-rw-r--r--src/tests/filesys/extended/dir-rm-parent.c16
-rw-r--r--src/tests/filesys/extended/dir-rm-parent.ck14
-rw-r--r--src/tests/filesys/extended/dir-rm-root-persistence.ck6
-rw-r--r--src/tests/filesys/extended/dir-rm-root.c13
-rw-r--r--src/tests/filesys/extended/dir-rm-root.ck11
-rw-r--r--src/tests/filesys/extended/dir-rm-tree-persistence.ck6
-rw-r--r--src/tests/filesys/extended/dir-rm-tree.c62
-rw-r--r--src/tests/filesys/extended/dir-rm-tree.ck14
-rw-r--r--src/tests/filesys/extended/dir-rmdir-persistence.ck6
-rw-r--r--src/tests/filesys/extended/dir-rmdir.c14
-rw-r--r--src/tests/filesys/extended/dir-rmdir.ck12
-rw-r--r--src/tests/filesys/extended/dir-under-file-persistence.ck6
-rw-r--r--src/tests/filesys/extended/dir-under-file.c13
-rw-r--r--src/tests/filesys/extended/dir-under-file.ck11
-rw-r--r--src/tests/filesys/extended/dir-vine-persistence.ck37
-rw-r--r--src/tests/filesys/extended/dir-vine.c85
-rw-r--r--src/tests/filesys/extended/dir-vine.ck11
-rw-r--r--src/tests/filesys/extended/grow-create-persistence.ck6
-rw-r--r--src/tests/filesys/extended/grow-create.c4
-rw-r--r--src/tests/filesys/extended/grow-create.ck13
-rw-r--r--src/tests/filesys/extended/grow-dir-lg-persistence.ck9
-rw-r--r--src/tests/filesys/extended/grow-dir-lg.c6
-rw-r--r--src/tests/filesys/extended/grow-dir-lg.ck61
-rw-r--r--src/tests/filesys/extended/grow-dir.inc41
-rw-r--r--src/tests/filesys/extended/grow-file-size-persistence.ck7
-rw-r--r--src/tests/filesys/extended/grow-file-size.c33
-rw-r--r--src/tests/filesys/extended/grow-file-size.ck17
-rw-r--r--src/tests/filesys/extended/grow-root-lg-persistence.ck9
-rw-r--r--src/tests/filesys/extended/grow-root-lg.c4
-rw-r--r--src/tests/filesys/extended/grow-root-lg.ck60
-rw-r--r--src/tests/filesys/extended/grow-root-sm-persistence.ck9
-rw-r--r--src/tests/filesys/extended/grow-root-sm.c4
-rw-r--r--src/tests/filesys/extended/grow-root-sm.ck30
-rw-r--r--src/tests/filesys/extended/grow-seq-lg-persistence.ck7
-rw-r--r--src/tests/filesys/extended/grow-seq-lg.c5
-rw-r--r--src/tests/filesys/extended/grow-seq-lg.ck17
-rw-r--r--src/tests/filesys/extended/grow-seq-sm-persistence.ck7
-rw-r--r--src/tests/filesys/extended/grow-seq-sm.c5
-rw-r--r--src/tests/filesys/extended/grow-seq-sm.ck17
-rw-r--r--src/tests/filesys/extended/grow-seq.inc20
-rw-r--r--src/tests/filesys/extended/grow-sparse-persistence.ck6
-rw-r--r--src/tests/filesys/extended/grow-sparse.c25
-rw-r--r--src/tests/filesys/extended/grow-sparse.ck17
-rw-r--r--src/tests/filesys/extended/grow-tell-persistence.ck7
-rw-r--r--src/tests/filesys/extended/grow-tell.c32
-rw-r--r--src/tests/filesys/extended/grow-tell.ck17
-rw-r--r--src/tests/filesys/extended/grow-two-files-persistence.ck9
-rw-r--r--src/tests/filesys/extended/grow-two-files.c62
-rw-r--r--src/tests/filesys/extended/grow-two-files.ck23
-rw-r--r--src/tests/filesys/extended/mk-tree.c67
-rw-r--r--src/tests/filesys/extended/mk-tree.h6
-rw-r--r--src/tests/filesys/extended/syn-rw-persistence.ck8
-rw-r--r--src/tests/filesys/extended/syn-rw.c35
-rw-r--r--src/tests/filesys/extended/syn-rw.ck20
-rw-r--r--src/tests/filesys/extended/syn-rw.h9
-rw-r--r--src/tests/filesys/extended/tar.c250
-rw-r--r--src/tests/filesys/seq-test.c37
-rw-r--r--src/tests/filesys/seq-test.h11
-rw-r--r--src/tests/internal/list.c174
-rw-r--r--src/tests/internal/stdio.c208
-rw-r--r--src/tests/internal/stdlib.c114
-rw-r--r--src/tests/klaar/Make.tests24
-rw-r--r--src/tests/klaar/child-simple.c15
-rw-r--r--src/tests/klaar/corrupt-elfbin0 -> 14398 bytes
-rw-r--r--src/tests/klaar/exec-corrupt.c14
-rw-r--r--src/tests/klaar/exec-corrupt.ck31
-rw-r--r--src/tests/klaar/low-mem.c15
-rw-r--r--src/tests/klaar/low-mem.ck11
-rw-r--r--src/tests/klaar/read-bad-buf.c24
-rw-r--r--src/tests/klaar/read-bad-buf.ck15
-rw-r--r--src/tests/klaar/sample.txt4
-rw-r--r--src/tests/lib.c196
-rw-r--r--src/tests/lib.h50
-rw-r--r--src/tests/lib.pm19
-rw-r--r--src/tests/main.c15
-rw-r--r--src/tests/main.h6
-rw-r--r--src/tests/make-grade152
-rw-r--r--src/tests/random.pm27
-rw-r--r--src/tests/tests.pm622
-rw-r--r--src/tests/threads/Grading6
-rw-r--r--src/tests/threads/Make.tests49
-rw-r--r--src/tests/threads/Rubric.alarm8
-rw-r--r--src/tests/threads/Rubric.mlfqs14
-rw-r--r--src/tests/threads/Rubric.priority15
-rw-r--r--src/tests/threads/alarm-multiple.ck4
-rw-r--r--src/tests/threads/alarm-negative.c15
-rw-r--r--src/tests/threads/alarm-negative.ck10
-rw-r--r--src/tests/threads/alarm-priority.c58
-rw-r--r--src/tests/threads/alarm-priority.ck19
-rw-r--r--src/tests/threads/alarm-simultaneous.c94
-rw-r--r--src/tests/threads/alarm-simultaneous.ck27
-rw-r--r--src/tests/threads/alarm-single.ck4
-rw-r--r--src/tests/threads/alarm-wait.c152
-rw-r--r--src/tests/threads/alarm-zero.c15
-rw-r--r--src/tests/threads/alarm-zero.ck10
-rw-r--r--src/tests/threads/alarm.pm32
-rw-r--r--src/tests/threads/mlfqs-block.c64
-rw-r--r--src/tests/threads/mlfqs-block.ck17
-rw-r--r--src/tests/threads/mlfqs-fair-2.ck7
-rw-r--r--src/tests/threads/mlfqs-fair-20.ck7
-rw-r--r--src/tests/threads/mlfqs-fair.c124
-rw-r--r--src/tests/threads/mlfqs-load-1.c60
-rw-r--r--src/tests/threads/mlfqs-load-1.ck15
-rw-r--r--src/tests/threads/mlfqs-load-60.c155
-rw-r--r--src/tests/threads/mlfqs-load-60.ck36
-rw-r--r--src/tests/threads/mlfqs-load-avg.c167
-rw-r--r--src/tests/threads/mlfqs-load-avg.ck36
-rw-r--r--src/tests/threads/mlfqs-nice-10.ck7
-rw-r--r--src/tests/threads/mlfqs-nice-2.ck7
-rw-r--r--src/tests/threads/mlfqs-recent-1.c144
-rw-r--r--src/tests/threads/mlfqs-recent-1.ck31
-rw-r--r--src/tests/threads/mlfqs.pm146
-rw-r--r--src/tests/threads/priority-change.c31
-rw-r--r--src/tests/threads/priority-change.ck14
-rw-r--r--src/tests/threads/priority-condvar.c53
-rw-r--r--src/tests/threads/priority-condvar.ck39
-rw-r--r--src/tests/threads/priority-donate-chain.c114
-rw-r--r--src/tests/threads/priority-donate-chain.ck46
-rw-r--r--src/tests/threads/priority-donate-lower.c51
-rw-r--r--src/tests/threads/priority-donate-lower.ck16
-rw-r--r--src/tests/threads/priority-donate-multiple.c77
-rw-r--r--src/tests/threads/priority-donate-multiple.ck19
-rw-r--r--src/tests/threads/priority-donate-multiple2.c90
-rw-r--r--src/tests/threads/priority-donate-multiple2.ck19
-rw-r--r--src/tests/threads/priority-donate-nest.c94
-rw-r--r--src/tests/threads/priority-donate-nest.ck19
-rw-r--r--src/tests/threads/priority-donate-one.c65
-rw-r--r--src/tests/threads/priority-donate-one.ck17
-rw-r--r--src/tests/threads/priority-donate-sema.c82
-rw-r--r--src/tests/threads/priority-donate-sema.ck16
-rw-r--r--src/tests/threads/priority-fifo.c99
-rw-r--r--src/tests/threads/priority-fifo.ck63
-rw-r--r--src/tests/threads/priority-preempt.c41
-rw-r--r--src/tests/threads/priority-preempt.ck16
-rw-r--r--src/tests/threads/priority-sema.c45
-rw-r--r--src/tests/threads/priority-sema.ck29
-rw-r--r--src/tests/threads/simplethreadtest.c68
-rw-r--r--src/tests/threads/tests.c104
-rw-r--r--src/tests/threads/tests.h43
-rw-r--r--src/tests/threads/threadtest.c250
-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
-rw-r--r--src/tests/vm/Grading12
-rw-r--r--src/tests/vm/Make.tests103
-rw-r--r--src/tests/vm/Rubric.functionality30
-rw-r--r--src/tests/vm/Rubric.robustness21
-rw-r--r--src/tests/vm/child-inherit.c16
-rw-r--r--src/tests/vm/child-linear.c36
-rw-r--r--src/tests/vm/child-mm-wrt.c24
-rw-r--r--src/tests/vm/child-qsort-mm.c25
-rw-r--r--src/tests/vm/child-qsort.c32
-rw-r--r--src/tests/vm/child-sort.c42
-rw-r--r--src/tests/vm/mmap-bad-fd.c15
-rw-r--r--src/tests/vm/mmap-bad-fd.ck15
-rw-r--r--src/tests/vm/mmap-clean.c53
-rw-r--r--src/tests/vm/mmap-clean.ck16
-rw-r--r--src/tests/vm/mmap-close.c27
-rw-r--r--src/tests/vm/mmap-close.ck11
-rw-r--r--src/tests/vm/mmap-exit.c22
-rw-r--r--src/tests/vm/mmap-exit.ck17
-rw-r--r--src/tests/vm/mmap-inherit.c32
-rw-r--r--src/tests/vm/mmap-inherit.ck16
-rw-r--r--src/tests/vm/mmap-misalign.c16
-rw-r--r--src/tests/vm/mmap-misalign.ck11
-rw-r--r--src/tests/vm/mmap-null.c15
-rw-r--r--src/tests/vm/mmap-null.ck11
-rw-r--r--src/tests/vm/mmap-over-code.c19
-rw-r--r--src/tests/vm/mmap-over-code.ck11
-rw-r--r--src/tests/vm/mmap-over-data.c21
-rw-r--r--src/tests/vm/mmap-over-data.ck11
-rw-r--r--src/tests/vm/mmap-over-stk.c19
-rw-r--r--src/tests/vm/mmap-over-stk.ck11
-rw-r--r--src/tests/vm/mmap-overlap.c20
-rw-r--r--src/tests/vm/mmap-overlap.ck13
-rw-r--r--src/tests/vm/mmap-read.c32
-rw-r--r--src/tests/vm/mmap-read.ck11
-rw-r--r--src/tests/vm/mmap-remove.c43
-rw-r--r--src/tests/vm/mmap-remove.ck14
-rw-r--r--src/tests/vm/mmap-shuffle.c38
-rw-r--r--src/tests/vm/mmap-shuffle.ck47
-rw-r--r--src/tests/vm/mmap-twice.c28
-rw-r--r--src/tests/vm/mmap-twice.ck15
-rw-r--r--src/tests/vm/mmap-unmap.c23
-rw-r--r--src/tests/vm/mmap-unmap.ck7
-rw-r--r--src/tests/vm/mmap-write.c32
-rw-r--r--src/tests/vm/mmap-write.ck13
-rw-r--r--src/tests/vm/mmap-zero.c27
-rw-r--r--src/tests/vm/mmap-zero.ck12
-rw-r--r--src/tests/vm/page-linear.c44
-rw-r--r--src/tests/vm/page-linear.ck14
-rw-r--r--src/tests/vm/page-merge-mm.c8
-rw-r--r--src/tests/vm/page-merge-mm.ck29
-rw-r--r--src/tests/vm/page-merge-par.c8
-rw-r--r--src/tests/vm/page-merge-par.ck29
-rw-r--r--src/tests/vm/page-merge-seq.c137
-rw-r--r--src/tests/vm/page-merge-seq.ck29
-rw-r--r--src/tests/vm/page-merge-stk.c8
-rw-r--r--src/tests/vm/page-merge-stk.ck29
-rw-r--r--src/tests/vm/page-parallel.c21
-rw-r--r--src/tests/vm/page-parallel.ck17
-rw-r--r--src/tests/vm/page-shuffle.c30
-rw-r--r--src/tests/vm/page-shuffle.ck44
-rw-r--r--src/tests/vm/parallel-merge.c149
-rw-r--r--src/tests/vm/parallel-merge.h6
-rw-r--r--src/tests/vm/process_death.pm22
-rw-r--r--src/tests/vm/pt-bad-addr.c11
-rw-r--r--src/tests/vm/pt-bad-addr.ck7
-rw-r--r--src/tests/vm/pt-bad-read.c16
-rw-r--r--src/tests/vm/pt-bad-read.ck10
-rw-r--r--src/tests/vm/pt-big-stk-obj.c20
-rw-r--r--src/tests/vm/pt-big-stk-obj.ck10
-rw-r--r--src/tests/vm/pt-grow-bad.c14
-rw-r--r--src/tests/vm/pt-grow-bad.ck9
-rw-r--r--src/tests/vm/pt-grow-pusha.c20
-rw-r--r--src/tests/vm/pt-grow-pusha.ck9
-rw-r--r--src/tests/vm/pt-grow-stack.c20
-rw-r--r--src/tests/vm/pt-grow-stack.ck10
-rw-r--r--src/tests/vm/pt-grow-stk-sc.c32
-rw-r--r--src/tests/vm/pt-grow-stk-sc.ck15
-rw-r--r--src/tests/vm/pt-write-code-2.c15
-rw-r--r--src/tests/vm/pt-write-code.c12
-rw-r--r--src/tests/vm/pt-write-code.ck7
-rw-r--r--src/tests/vm/pt-write-code2.ck10
-rw-r--r--src/tests/vm/qsort.c136
-rw-r--r--src/tests/vm/qsort.h8
-rw-r--r--src/tests/vm/sample.inc19
-rw-r--r--src/tests/vm/sample.txt17
438 files changed, 14254 insertions, 0 deletions
diff --git a/src/tests/Algorithm/Diff.pm b/src/tests/Algorithm/Diff.pm
new file mode 100644
index 0000000..904c530
--- /dev/null
+++ b/src/tests/Algorithm/Diff.pm
@@ -0,0 +1,1713 @@
+package Algorithm::Diff;
+# Skip to first "=head" line for documentation.
+use strict;
+
+use integer; # see below in _replaceNextLargerWith() for mod to make
+ # if you don't use this
+use vars qw( $VERSION @EXPORT_OK );
+$VERSION = 1.19_01;
+# ^ ^^ ^^-- Incremented at will
+# | \+----- Incremented for non-trivial changes to features
+# \-------- Incremented for fundamental changes
+require Exporter;
+*import = \&Exporter::import;
+@EXPORT_OK = qw(
+ prepare LCS LCDidx LCS_length
+ diff sdiff compact_diff
+ traverse_sequences traverse_balanced
+);
+
+# McIlroy-Hunt diff algorithm
+# Adapted from the Smalltalk code of Mario I. Wolczko, <mario@wolczko.com>
+# by Ned Konz, perl@bike-nomad.com
+# Updates by Tye McQueen, http://perlmonks.org/?node=tye
+
+# Create a hash that maps each element of $aCollection to the set of
+# positions it occupies in $aCollection, restricted to the elements
+# within the range of indexes specified by $start and $end.
+# The fourth parameter is a subroutine reference that will be called to
+# generate a string to use as a key.
+# Additional parameters, if any, will be passed to this subroutine.
+#
+# my $hashRef = _withPositionsOfInInterval( \@array, $start, $end, $keyGen );
+
+sub _withPositionsOfInInterval
+{
+ my $aCollection = shift; # array ref
+ my $start = shift;
+ my $end = shift;
+ my $keyGen = shift;
+ my %d;
+ my $index;
+ for ( $index = $start ; $index <= $end ; $index++ )
+ {
+ my $element = $aCollection->[$index];
+ my $key = &$keyGen( $element, @_ );
+ if ( exists( $d{$key} ) )
+ {
+ unshift ( @{ $d{$key} }, $index );
+ }
+ else
+ {
+ $d{$key} = [$index];
+ }
+ }
+ return wantarray ? %d : \%d;
+}
+
+# Find the place at which aValue would normally be inserted into the
+# array. If that place is already occupied by aValue, do nothing, and
+# return undef. If the place does not exist (i.e., it is off the end of
+# the array), add it to the end, otherwise replace the element at that
+# point with aValue. It is assumed that the array's values are numeric.
+# This is where the bulk (75%) of the time is spent in this module, so
+# try to make it fast!
+
+sub _replaceNextLargerWith
+{
+ my ( $array, $aValue, $high ) = @_;
+ $high ||= $#$array;
+
+ # off the end?
+ if ( $high == -1 || $aValue > $array->[-1] )
+ {
+ push ( @$array, $aValue );
+ return $high + 1;
+ }
+
+ # binary search for insertion point...
+ my $low = 0;
+ my $index;
+ my $found;
+ while ( $low <= $high )
+ {
+ $index = ( $high + $low ) / 2;
+
+ # $index = int(( $high + $low ) / 2); # without 'use integer'
+ $found = $array->[$index];
+
+ if ( $aValue == $found )
+ {
+ return undef;
+ }
+ elsif ( $aValue > $found )
+ {
+ $low = $index + 1;
+ }
+ else
+ {
+ $high = $index - 1;
+ }
+ }
+
+ # now insertion point is in $low.
+ $array->[$low] = $aValue; # overwrite next larger
+ return $low;
+}
+
+# This method computes the longest common subsequence in $a and $b.
+
+# Result is array or ref, whose contents is such that
+# $a->[ $i ] == $b->[ $result[ $i ] ]
+# foreach $i in ( 0 .. $#result ) if $result[ $i ] is defined.
+
+# An additional argument may be passed; this is a hash or key generating
+# function that should return a string that uniquely identifies the given
+# element. It should be the case that if the key is the same, the elements
+# will compare the same. If this parameter is undef or missing, the key
+# will be the element as a string.
+
+# By default, comparisons will use "eq" and elements will be turned into keys
+# using the default stringizing operator '""'.
+
+# Additional parameters, if any, will be passed to the key generation
+# routine.
+
+sub _longestCommonSubsequence
+{
+ my $a = shift; # array ref or hash ref
+ my $b = shift; # array ref or hash ref
+ my $counting = shift; # scalar
+ my $keyGen = shift; # code ref
+ my $compare; # code ref
+
+ if ( ref($a) eq 'HASH' )
+ { # prepared hash must be in $b
+ my $tmp = $b;
+ $b = $a;
+ $a = $tmp;
+ }
+
+ # Check for bogus (non-ref) argument values
+ if ( !ref($a) || !ref($b) )
+ {
+ my @callerInfo = caller(1);
+ die 'error: must pass array or hash references to ' . $callerInfo[3];
+ }
+
+ # set up code refs
+ # Note that these are optimized.
+ if ( !defined($keyGen) ) # optimize for strings
+ {
+ $keyGen = sub { $_[0] };
+ $compare = sub { my ( $a, $b ) = @_; $a eq $b };
+ }
+ else
+ {
+ $compare = sub {
+ my $a = shift;
+ my $b = shift;
+ &$keyGen( $a, @_ ) eq &$keyGen( $b, @_ );
+ };
+ }
+
+ my ( $aStart, $aFinish, $matchVector ) = ( 0, $#$a, [] );
+ my ( $prunedCount, $bMatches ) = ( 0, {} );
+
+ if ( ref($b) eq 'HASH' ) # was $bMatches prepared for us?
+ {
+ $bMatches = $b;
+ }
+ else
+ {
+ my ( $bStart, $bFinish ) = ( 0, $#$b );
+
+ # First we prune off any common elements at the beginning
+ while ( $aStart <= $aFinish
+ and $bStart <= $bFinish
+ and &$compare( $a->[$aStart], $b->[$bStart], @_ ) )
+ {
+ $matchVector->[ $aStart++ ] = $bStart++;
+ $prunedCount++;
+ }
+
+ # now the end
+ while ( $aStart <= $aFinish
+ and $bStart <= $bFinish
+ and &$compare( $a->[$aFinish], $b->[$bFinish], @_ ) )
+ {
+ $matchVector->[ $aFinish-- ] = $bFinish--;
+ $prunedCount++;
+ }
+
+ # Now compute the equivalence classes of positions of elements
+ $bMatches =
+ _withPositionsOfInInterval( $b, $bStart, $bFinish, $keyGen, @_ );
+ }
+ my $thresh = [];
+ my $links = [];
+
+ my ( $i, $ai, $j, $k );
+ for ( $i = $aStart ; $i <= $aFinish ; $i++ )
+ {
+ $ai = &$keyGen( $a->[$i], @_ );
+ if ( exists( $bMatches->{$ai} ) )
+ {
+ $k = 0;
+ for $j ( @{ $bMatches->{$ai} } )
+ {
+
+ # optimization: most of the time this will be true
+ if ( $k and $thresh->[$k] > $j and $thresh->[ $k - 1 ] < $j )
+ {
+ $thresh->[$k] = $j;
+ }
+ else
+ {
+ $k = _replaceNextLargerWith( $thresh, $j, $k );
+ }
+
+ # oddly, it's faster to always test this (CPU cache?).
+ if ( defined($k) )
+ {
+ $links->[$k] =
+ [ ( $k ? $links->[ $k - 1 ] : undef ), $i, $j ];
+ }
+ }
+ }
+ }
+
+ if (@$thresh)
+ {
+ return $prunedCount + @$thresh if $counting;
+ for ( my $link = $links->[$#$thresh] ; $link ; $link = $link->[0] )
+ {
+ $matchVector->[ $link->[1] ] = $link->[2];
+ }
+ }
+ elsif ($counting)
+ {
+ return $prunedCount;
+ }
+
+ return wantarray ? @$matchVector : $matchVector;
+}
+
+sub traverse_sequences
+{
+ my $a = shift; # array ref
+ my $b = shift; # array ref
+ my $callbacks = shift || {};
+ my $keyGen = shift;
+ my $matchCallback = $callbacks->{'MATCH'} || sub { };
+ my $discardACallback = $callbacks->{'DISCARD_A'} || sub { };
+ my $finishedACallback = $callbacks->{'A_FINISHED'};
+ my $discardBCallback = $callbacks->{'DISCARD_B'} || sub { };
+ my $finishedBCallback = $callbacks->{'B_FINISHED'};
+ my $matchVector = _longestCommonSubsequence( $a, $b, 0, $keyGen, @_ );
+
+ # Process all the lines in @$matchVector
+ my $lastA = $#$a;
+ my $lastB = $#$b;
+ my $bi = 0;
+ my $ai;
+
+ for ( $ai = 0 ; $ai <= $#$matchVector ; $ai++ )
+ {
+ my $bLine = $matchVector->[$ai];
+ if ( defined($bLine) ) # matched
+ {
+ &$discardBCallback( $ai, $bi++, @_ ) while $bi < $bLine;
+ &$matchCallback( $ai, $bi++, @_ );
+ }
+ else
+ {
+ &$discardACallback( $ai, $bi, @_ );
+ }
+ }
+
+ # The last entry (if any) processed was a match.
+ # $ai and $bi point just past the last matching lines in their sequences.
+
+ while ( $ai <= $lastA or $bi <= $lastB )
+ {
+
+ # last A?
+ if ( $ai == $lastA + 1 and $bi <= $lastB )
+ {
+ if ( defined($finishedACallback) )
+ {
+ &$finishedACallback( $lastA, @_ );
+ $finishedACallback = undef;
+ }
+ else
+ {
+ &$discardBCallback( $ai, $bi++, @_ ) while $bi <= $lastB;
+ }
+ }
+
+ # last B?
+ if ( $bi == $lastB + 1 and $ai <= $lastA )
+ {
+ if ( defined($finishedBCallback) )
+ {
+ &$finishedBCallback( $lastB, @_ );
+ $finishedBCallback = undef;
+ }
+ else
+ {
+ &$discardACallback( $ai++, $bi, @_ ) while $ai <= $lastA;
+ }
+ }
+
+ &$discardACallback( $ai++, $bi, @_ ) if $ai <= $lastA;
+ &$discardBCallback( $ai, $bi++, @_ ) if $bi <= $lastB;
+ }
+
+ return 1;
+}
+
+sub traverse_balanced
+{
+ my $a = shift; # array ref
+ my $b = shift; # array ref
+ my $callbacks = shift || {};
+ my $keyGen = shift;
+ my $matchCallback = $callbacks->{'MATCH'} || sub { };
+ my $discardACallback = $callbacks->{'DISCARD_A'} || sub { };
+ my $discardBCallback = $callbacks->{'DISCARD_B'} || sub { };
+ my $changeCallback = $callbacks->{'CHANGE'};
+ my $matchVector = _longestCommonSubsequence( $a, $b, 0, $keyGen, @_ );
+
+ # Process all the lines in match vector
+ my $lastA = $#$a;
+ my $lastB = $#$b;
+ my $bi = 0;
+ my $ai = 0;
+ my $ma = -1;
+ my $mb;
+
+ while (1)
+ {
+
+ # Find next match indices $ma and $mb
+ do {
+ $ma++;
+ } while(
+ $ma <= $#$matchVector
+ && !defined $matchVector->[$ma]
+ );
+
+ last if $ma > $#$matchVector; # end of matchVector?
+ $mb = $matchVector->[$ma];
+
+ # Proceed with discard a/b or change events until
+ # next match
+ while ( $ai < $ma || $bi < $mb )
+ {
+
+ if ( $ai < $ma && $bi < $mb )
+ {
+
+ # Change
+ if ( defined $changeCallback )
+ {
+ &$changeCallback( $ai++, $bi++, @_ );
+ }
+ else
+ {
+ &$discardACallback( $ai++, $bi, @_ );
+ &$discardBCallback( $ai, $bi++, @_ );
+ }
+ }
+ elsif ( $ai < $ma )
+ {
+ &$discardACallback( $ai++, $bi, @_ );
+ }
+ else
+ {
+
+ # $bi < $mb
+ &$discardBCallback( $ai, $bi++, @_ );
+ }
+ }
+
+ # Match
+ &$matchCallback( $ai++, $bi++, @_ );
+ }
+
+ while ( $ai <= $lastA || $bi <= $lastB )
+ {
+ if ( $ai <= $lastA && $bi <= $lastB )
+ {
+
+ # Change
+ if ( defined $changeCallback )
+ {
+ &$changeCallback( $ai++, $bi++, @_ );
+ }
+ else
+ {
+ &$discardACallback( $ai++, $bi, @_ );
+ &$discardBCallback( $ai, $bi++, @_ );
+ }
+ }
+ elsif ( $ai <= $lastA )
+ {
+ &$discardACallback( $ai++, $bi, @_ );
+ }
+ else
+ {
+
+ # $bi <= $lastB
+ &$discardBCallback( $ai, $bi++, @_ );
+ }
+ }
+
+ return 1;
+}
+
+sub prepare
+{
+ my $a = shift; # array ref
+ my $keyGen = shift; # code ref
+
+ # set up code ref
+ $keyGen = sub { $_[0] } unless defined($keyGen);
+
+ return scalar _withPositionsOfInInterval( $a, 0, $#$a, $keyGen, @_ );
+}
+
+sub LCS
+{
+ my $a = shift; # array ref
+ my $b = shift; # array ref or hash ref
+ my $matchVector = _longestCommonSubsequence( $a, $b, 0, @_ );
+ my @retval;
+ my $i;
+ for ( $i = 0 ; $i <= $#$matchVector ; $i++ )
+ {
+ if ( defined( $matchVector->[$i] ) )
+ {
+ push ( @retval, $a->[$i] );
+ }
+ }
+ return wantarray ? @retval : \@retval;
+}
+
+sub LCS_length
+{
+ my $a = shift; # array ref
+ my $b = shift; # array ref or hash ref
+ return _longestCommonSubsequence( $a, $b, 1, @_ );
+}
+
+sub LCSidx
+{
+ my $a= shift @_;
+ my $b= shift @_;
+ my $match= _longestCommonSubsequence( $a, $b, 0, @_ );
+ my @am= grep defined $match->[$_], 0..$#$match;
+ my @bm= @{$match}[@am];
+ return \@am, \@bm;
+}
+
+sub compact_diff
+{
+ my $a= shift @_;
+ my $b= shift @_;
+ my( $am, $bm )= LCSidx( $a, $b, @_ );
+ my @cdiff;
+ my( $ai, $bi )= ( 0, 0 );
+ push @cdiff, $ai, $bi;
+ while( 1 ) {
+ while( @$am && $ai == $am->[0] && $bi == $bm->[0] ) {
+ shift @$am;
+ shift @$bm;
+ ++$ai, ++$bi;
+ }
+ push @cdiff, $ai, $bi;
+ last if ! @$am;
+ $ai = $am->[0];
+ $bi = $bm->[0];
+ push @cdiff, $ai, $bi;
+ }
+ push @cdiff, 0+@$a, 0+@$b
+ if $ai < @$a || $bi < @$b;
+ return wantarray ? @cdiff : \@cdiff;
+}
+
+sub diff
+{
+ my $a = shift; # array ref
+ my $b = shift; # array ref
+ my $retval = [];
+ my $hunk = [];
+ my $discard = sub {
+ push @$hunk, [ '-', $_[0], $a->[ $_[0] ] ];
+ };
+ my $add = sub {
+ push @$hunk, [ '+', $_[1], $b->[ $_[1] ] ];
+ };
+ my $match = sub {
+ push @$retval, $hunk
+ if 0 < @$hunk;
+ $hunk = []
+ };
+ traverse_sequences( $a, $b,
+ { MATCH => $match, DISCARD_A => $discard, DISCARD_B => $add }, @_ );
+ &$match();
+ return wantarray ? @$retval : $retval;
+}
+
+sub sdiff
+{
+ my $a = shift; # array ref
+ my $b = shift; # array ref
+ my $retval = [];
+ my $discard = sub { push ( @$retval, [ '-', $a->[ $_[0] ], "" ] ) };
+ my $add = sub { push ( @$retval, [ '+', "", $b->[ $_[1] ] ] ) };
+ my $change = sub {
+ push ( @$retval, [ 'c', $a->[ $_[0] ], $b->[ $_[1] ] ] );
+ };
+ my $match = sub {
+ push ( @$retval, [ 'u', $a->[ $_[0] ], $b->[ $_[1] ] ] );
+ };
+ traverse_balanced(
+ $a,
+ $b,
+ {
+ MATCH => $match,
+ DISCARD_A => $discard,
+ DISCARD_B => $add,
+ CHANGE => $change,
+ },
+ @_
+ );
+ return wantarray ? @$retval : $retval;
+}
+
+########################################
+my $Root= __PACKAGE__;
+package Algorithm::Diff::_impl;
+use strict;
+
+sub _Idx() { 0 } # $me->[_Idx]: Ref to array of hunk indices
+ # 1 # $me->[1]: Ref to first sequence
+ # 2 # $me->[2]: Ref to second sequence
+sub _End() { 3 } # $me->[_End]: Diff between forward and reverse pos
+sub _Same() { 4 } # $me->[_Same]: 1 if pos 1 contains unchanged items
+sub _Base() { 5 } # $me->[_Base]: Added to range's min and max
+sub _Pos() { 6 } # $me->[_Pos]: Which hunk is currently selected
+sub _Off() { 7 } # $me->[_Off]: Offset into _Idx for current position
+sub _Min() { -2 } # Added to _Off to get min instead of max+1
+
+sub Die
+{
+ require Carp;
+ Carp::confess( @_ );
+}
+
+sub _ChkPos
+{
+ my( $me )= @_;
+ return if $me->[_Pos];
+ my $meth= ( caller(1) )[3];
+ Die( "Called $meth on 'reset' object" );
+}
+
+sub _ChkSeq
+{
+ my( $me, $seq )= @_;
+ return $seq + $me->[_Off]
+ if 1 == $seq || 2 == $seq;
+ my $meth= ( caller(1) )[3];
+ Die( "$meth: Invalid sequence number ($seq); must be 1 or 2" );
+}
+
+sub getObjPkg
+{
+ my( $us )= @_;
+ return ref $us if ref $us;
+ return $us . "::_obj";
+}
+
+sub new
+{
+ my( $us, $seq1, $seq2, $opts ) = @_;
+ my @args;
+ for( $opts->{keyGen} ) {
+ push @args, $_ if $_;
+ }
+ for( $opts->{keyGenArgs} ) {
+ push @args, @$_ if $_;
+ }
+ my $cdif= Algorithm::Diff::compact_diff( $seq1, $seq2, @args );
+ my $same= 1;
+ if( 0 == $cdif->[2] && 0 == $cdif->[3] ) {
+ $same= 0;
+ splice @$cdif, 0, 2;
+ }
+ my @obj= ( $cdif, $seq1, $seq2 );
+ $obj[_End] = (1+@$cdif)/2;
+ $obj[_Same] = $same;
+ $obj[_Base] = 0;
+ my $me = bless \@obj, $us->getObjPkg();
+ $me->Reset( 0 );
+ return $me;
+}
+
+sub Reset
+{
+ my( $me, $pos )= @_;
+ $pos= int( $pos || 0 );
+ $pos += $me->[_End]
+ if $pos < 0;
+ $pos= 0
+ if $pos < 0 || $me->[_End] <= $pos;
+ $me->[_Pos]= $pos || !1;
+ $me->[_Off]= 2*$pos - 1;
+ return $me;
+}
+
+sub Base
+{
+ my( $me, $base )= @_;
+ my $oldBase= $me->[_Base];
+ $me->[_Base]= 0+$base if defined $base;
+ return $oldBase;
+}
+
+sub Copy
+{
+ my( $me, $pos, $base )= @_;
+ my @obj= @$me;
+ my $you= bless \@obj, ref($me);
+ $you->Reset( $pos ) if defined $pos;
+ $you->Base( $base );
+ return $you;
+}
+
+sub Next {
+ my( $me, $steps )= @_;
+ $steps= 1 if ! defined $steps;
+ if( $steps ) {
+ my $pos= $me->[_Pos];
+ my $new= $pos + $steps;
+ $new= 0 if $pos && $new < 0;
+ $me->Reset( $new )
+ }
+ return $me->[_Pos];
+}
+
+sub Prev {
+ my( $me, $steps )= @_;
+ $steps= 1 if ! defined $steps;
+ my $pos= $me->Next(-$steps);
+ $pos -= $me->[_End] if $pos;
+ return $pos;
+}
+
+sub Diff {
+ my( $me )= @_;
+ $me->_ChkPos();
+ return 0 if $me->[_Same] == ( 1 & $me->[_Pos] );
+ my $ret= 0;
+ my $off= $me->[_Off];
+ for my $seq ( 1, 2 ) {
+ $ret |= $seq
+ if $me->[_Idx][ $off + $seq + _Min ]
+ < $me->[_Idx][ $off + $seq ];
+ }
+ return $ret;
+}
+
+sub Min {
+ my( $me, $seq, $base )= @_;
+ $me->_ChkPos();
+ my $off= $me->_ChkSeq($seq);
+ $base= $me->[_Base] if !defined $base;
+ return $base + $me->[_Idx][ $off + _Min ];
+}
+
+sub Max {
+ my( $me, $seq, $base )= @_;
+ $me->_ChkPos();
+ my $off= $me->_ChkSeq($seq);
+ $base= $me->[_Base] if !defined $base;
+ return $base + $me->[_Idx][ $off ] -1;
+}
+
+sub Range {
+ my( $me, $seq, $base )= @_;
+ $me->_ChkPos();
+ my $off = $me->_ChkSeq($seq);
+ if( !wantarray ) {
+ return $me->[_Idx][ $off ]
+ - $me->[_Idx][ $off + _Min ];
+ }
+ $base= $me->[_Base] if !defined $base;
+ return ( $base + $me->[_Idx][ $off + _Min ] )
+ .. ( $base + $me->[_Idx][ $off ] - 1 );
+}
+
+sub Items {
+ my( $me, $seq )= @_;
+ $me->_ChkPos();
+ my $off = $me->_ChkSeq($seq);
+ if( !wantarray ) {
+ return $me->[_Idx][ $off ]
+ - $me->[_Idx][ $off + _Min ];
+ }
+ return
+ @{$me->[$seq]}[
+ $me->[_Idx][ $off + _Min ]
+ .. ( $me->[_Idx][ $off ] - 1 )
+ ];
+}
+
+sub Same {
+ my( $me )= @_;
+ $me->_ChkPos();
+ return wantarray ? () : 0
+ if $me->[_Same] != ( 1 & $me->[_Pos] );
+ return $me->Items(1);
+}
+
+my %getName;
+BEGIN {
+ %getName= (
+ same => \&Same,
+ diff => \&Diff,
+ base => \&Base,
+ min => \&Min,
+ max => \&Max,
+ range=> \&Range,
+ items=> \&Items, # same thing
+ );
+}
+
+sub Get
+{
+ my $me= shift @_;
+ $me->_ChkPos();
+ my @value;
+ for my $arg ( @_ ) {
+ for my $word ( split ' ', $arg ) {
+ my $meth;
+ if( $word !~ /^(-?\d+)?([a-zA-Z]+)([12])?$/
+ || not $meth= $getName{ lc $2 }
+ ) {
+ Die( $Root, ", Get: Invalid request ($word)" );
+ }
+ my( $base, $name, $seq )= ( $1, $2, $3 );
+ push @value, scalar(
+ 4 == length($name)
+ ? $meth->( $me )
+ : $meth->( $me, $seq, $base )
+ );
+ }
+ }
+ if( wantarray ) {
+ return @value;
+ } elsif( 1 == @value ) {
+ return $value[0];
+ }
+ Die( 0+@value, " values requested from ",
+ $Root, "'s Get in scalar context" );
+}
+
+
+my $Obj= getObjPkg($Root);
+no strict 'refs';
+
+for my $meth ( qw( new getObjPkg ) ) {
+ *{$Root."::".$meth} = \&{$meth};
+ *{$Obj ."::".$meth} = \&{$meth};
+}
+for my $meth ( qw(
+ Next Prev Reset Copy Base Diff
+ Same Items Range Min Max Get
+ _ChkPos _ChkSeq
+) ) {
+ *{$Obj."::".$meth} = \&{$meth};
+}
+
+1;
+__END__
+
+=head1 NAME
+
+Algorithm::Diff - Compute `intelligent' differences between two files / lists
+
+=head1 SYNOPSIS
+
+ require Algorithm::Diff;
+
+ # This example produces traditional 'diff' output:
+
+ my $diff = Algorithm::Diff->new( \@seq1, \@seq2 );
+
+ $diff->Base( 1 ); # Return line numbers, not indices
+ while( $diff->Next() ) {
+ next if $diff->Same();
+ my $sep = '';
+ if( ! $diff->Items(2) ) {
+ sprintf "%d,%dd%d\n",
+ $diff->Get(qw( Min1 Max1 Max2 ));
+ } elsif( ! $diff->Items(1) ) {
+ sprint "%da%d,%d\n",
+ $diff->Get(qw( Max1 Min2 Max2 ));
+ } else {
+ $sep = "---\n";
+ sprintf "%d,%dc%d,%d\n",
+ $diff->Get(qw( Min1 Max1 Min2 Max2 ));
+ }
+ print "< $_" for $diff->Items(1);
+ print $sep;
+ print "> $_" for $diff->Items(2);
+ }
+
+
+ # Alternate interfaces:
+
+ use Algorithm::Diff qw(
+ LCS LCS_length LCSidx
+ diff sdiff compact_diff
+ traverse_sequences traverse_balanced );
+
+ @lcs = LCS( \@seq1, \@seq2 );
+ $lcsref = LCS( \@seq1, \@seq2 );
+ $count = LCS_length( \@seq1, \@seq2 );
+
+ ( $seq1idxref, $seq2idxref ) = LCSidx( \@seq1, \@seq2 );
+
+
+ # Complicated interfaces:
+
+ @diffs = diff( \@seq1, \@seq2 );
+
+ @sdiffs = sdiff( \@seq1, \@seq2 );
+
+ @cdiffs = compact_diff( \@seq1, \@seq2 );
+
+ traverse_sequences(
+ \@seq1,
+ \@seq2,
+ { MATCH => \&callback1,
+ DISCARD_A => \&callback2,
+ DISCARD_B => \&callback3,
+ },
+ \&key_generator,
+ @extra_args,
+ );
+
+ traverse_balanced(
+ \@seq1,
+ \@seq2,
+ { MATCH => \&callback1,
+ DISCARD_A => \&callback2,
+ DISCARD_B => \&callback3,
+ CHANGE => \&callback4,
+ },
+ \&key_generator,
+ @extra_args,
+ );
+
+
+=head1 INTRODUCTION
+
+(by Mark-Jason Dominus)
+
+I once read an article written by the authors of C<diff>; they said
+that they worked very hard on the algorithm until they found the
+right one.
+
+I think what they ended up using (and I hope someone will correct me,
+because I am not very confident about this) was the `longest common
+subsequence' method. In the LCS problem, you have two sequences of
+items:
+
+ a b c d f g h j q z
+
+ a b c d e f g i j k r x y z
+
+and you want to find the longest sequence of items that is present in
+both original sequences in the same order. That is, you want to find
+a new sequence I<S> which can be obtained from the first sequence by
+deleting some items, and from the secend sequence by deleting other
+items. You also want I<S> to be as long as possible. In this case I<S>
+is
+
+ a b c d f g j z
+
+From there it's only a small step to get diff-like output:
+
+ e h i k q r x y
+ + - + + - + + +
+
+This module solves the LCS problem. It also includes a canned function
+to generate C<diff>-like output.
+
+It might seem from the example above that the LCS of two sequences is
+always pretty obvious, but that's not always the case, especially when
+the two sequences have many repeated elements. For example, consider
+
+ a x b y c z p d q
+ a b c a x b y c z
+
+A naive approach might start by matching up the C<a> and C<b> that
+appear at the beginning of each sequence, like this:
+
+ a x b y c z p d q
+ a b c a b y c z
+
+This finds the common subsequence C<a b c z>. But actually, the LCS
+is C<a x b y c z>:
+
+ a x b y c z p d q
+ a b c a x b y c z
+
+or
+
+ a x b y c z p d q
+ a b c a x b y c z
+
+=head1 USAGE
+
+(See also the README file and several example
+scripts include with this module.)
+
+This module now provides an object-oriented interface that uses less
+memory and is easier to use than most of the previous procedural
+interfaces. It also still provides several exportable functions. We'll
+deal with these in ascending order of difficulty: C<LCS>,
+C<LCS_length>, C<LCSidx>, OO interface, C<prepare>, C<diff>, C<sdiff>,
+C<traverse_sequences>, and C<traverse_balanced>.
+
+=head2 C<LCS>
+
+Given references to two lists of items, LCS returns an array containing
+their longest common subsequence. In scalar context, it returns a
+reference to such a list.
+
+ @lcs = LCS( \@seq1, \@seq2 );
+ $lcsref = LCS( \@seq1, \@seq2 );
+
+C<LCS> may be passed an optional third parameter; this is a CODE
+reference to a key generation function. See L</KEY GENERATION
+FUNCTIONS>.
+
+ @lcs = LCS( \@seq1, \@seq2, \&keyGen, @args );
+ $lcsref = LCS( \@seq1, \@seq2, \&keyGen, @args );
+
+Additional parameters, if any, will be passed to the key generation
+routine.
+
+=head2 C<LCS_length>
+
+This is just like C<LCS> except it only returns the length of the
+longest common subsequence. This provides a performance gain of about
+9% compared to C<LCS>.
+
+=head2 C<LCSidx>
+
+Like C<LCS> except it returns references to two arrays. The first array
+contains the indices into @seq1 where the LCS items are located. The
+second array contains the indices into @seq2 where the LCS items are located.
+
+Therefore, the following three lists will contain the same values:
+
+ my( $idx1, $idx2 ) = LCSidx( \@seq1, \@seq2 );
+ my @list1 = @seq1[ @$idx1 ];
+ my @list2 = @seq2[ @$idx2 ];
+ my @list3 = LCS( \@seq1, \@seq2 );
+
+=head2 C<new>
+
+ $diff = Algorithm::Diffs->new( \@seq1, \@seq2 );
+ $diff = Algorithm::Diffs->new( \@seq1, \@seq2, \%opts );
+
+C<new> computes the smallest set of additions and deletions necessary
+to turn the first sequence into the second and compactly records them
+in the object.
+
+You use the object to iterate over I<hunks>, where each hunk represents
+a contiguous section of items which should be added, deleted, replaced,
+or left unchanged.
+
+=over 4
+
+The following summary of all of the methods looks a lot like Perl code
+but some of the symbols have different meanings:
+
+ [ ] Encloses optional arguments
+ : Is followed by the default value for an optional argument
+ | Separates alternate return results
+
+Method summary:
+
+ $obj = Algorithm::Diff->new( \@seq1, \@seq2, [ \%opts ] );
+ $pos = $obj->Next( [ $count : 1 ] );
+ $revPos = $obj->Prev( [ $count : 1 ] );
+ $obj = $obj->Reset( [ $pos : 0 ] );
+ $copy = $obj->Copy( [ $pos, [ $newBase ] ] );
+ $oldBase = $obj->Base( [ $newBase ] );
+
+Note that all of the following methods C<die> if used on an object that
+is "reset" (not currently pointing at any hunk).
+
+ $bits = $obj->Diff( );
+ @items|$cnt = $obj->Same( );
+ @items|$cnt = $obj->Items( $seqNum );
+ @idxs |$cnt = $obj->Range( $seqNum, [ $base ] );
+ $minIdx = $obj->Min( $seqNum, [ $base ] );
+ $maxIdx = $obj->Max( $seqNum, [ $base ] );
+ @values = $obj->Get( @names );
+
+Passing in C<undef> for an optional argument is always treated the same
+as if no argument were passed in.
+
+=item C<Next>
+
+ $pos = $diff->Next(); # Move forward 1 hunk
+ $pos = $diff->Next( 2 ); # Move forward 2 hunks
+ $pos = $diff->Next(-5); # Move backward 5 hunks
+
+C<Next> moves the object to point at the next hunk. The object starts
+out "reset", which means it isn't pointing at any hunk. If the object
+is reset, then C<Next()> moves to the first hunk.
+
+C<Next> returns a true value iff the move didn't go past the last hunk.
+So C<Next(0)> will return true iff the object is not reset.
+
+Actually, C<Next> returns the object's new position, which is a number
+between 1 and the number of hunks (inclusive), or returns a false value.
+
+=item C<Prev>
+
+C<Prev($N)> is almost identical to C<Next(-$N)>; it moves to the $Nth
+previous hunk. On a 'reset' object, C<Prev()> [and C<Next(-1)>] move
+to the last hunk.
+
+The position returned by C<Prev> is relative to the I<end> of the
+hunks; -1 for the last hunk, -2 for the second-to-last, etc.
+
+=item C<Reset>
+
+ $diff->Reset(); # Reset the object's position
+ $diff->Reset($pos); # Move to the specified hunk
+ $diff->Reset(1); # Move to the first hunk
+ $diff->Reset(-1); # Move to the last hunk
+
+C<Reset> returns the object, so, for example, you could use
+C<< $diff->Reset()->Next(-1) >> to get the number of hunks.
+
+=item C<Copy>
+
+ $copy = $diff->Copy( $newPos, $newBase );
+
+C<Copy> returns a copy of the object. The copy and the orignal object
+share most of their data, so making copies takes very little memory.
+The copy maintains its own position (separate from the original), which
+is the main purpose of copies. It also maintains its own base.
+
+By default, the copy's position starts out the same as the original
+object's position. But C<Copy> takes an optional first argument to set the
+new position, so the following three snippets are equivalent:
+
+ $copy = $diff->Copy($pos);
+
+ $copy = $diff->Copy();
+ $copy->Reset($pos);
+
+ $copy = $diff->Copy()->Reset($pos);
+
+C<Copy> takes an optional second argument to set the base for
+the copy. If you wish to change the base of the copy but leave
+the position the same as in the original, here are two
+equivalent ways:
+
+ $copy = $diff->Copy();
+ $copy->Base( 0 );
+
+ $copy = $diff->Copy(undef,0);
+
+Here are two equivalent way to get a "reset" copy:
+
+ $copy = $diff->Copy(0);
+
+ $copy = $diff->Copy()->Reset();
+
+=item C<Diff>
+
+ $bits = $obj->Diff();
+
+C<Diff> returns a true value iff the current hunk contains items that are
+different between the two sequences. It actually returns one of the
+follow 4 values:
+
+=over 4
+
+=item 3
+
+C<3==(1|2)>. This hunk contains items from @seq1 and the items
+from @seq2 that should replace them. Both sequence 1 and 2
+contain changed items so both the 1 and 2 bits are set.
+
+=item 2
+
+This hunk only contains items from @seq2 that should be inserted (not
+items from @seq1). Only sequence 2 contains changed items so only the 2
+bit is set.
+
+=item 1
+
+This hunk only contains items from @seq1 that should be deleted (not
+items from @seq2). Only sequence 1 contains changed items so only the 1
+bit is set.
+
+=item 0
+
+This means that the items in this hunk are the same in both sequences.
+Neither sequence 1 nor 2 contain changed items so neither the 1 nor the
+2 bits are set.
+
+=back
+
+=item C<Same>
+
+C<Same> returns a true value iff the current hunk contains items that
+are the same in both sequences. It actually returns the list of items
+if they are the same or an emty list if they aren't. In a scalar
+context, it returns the size of the list.
+
+=item C<Items>
+
+ $count = $diff->Items(2);
+ @items = $diff->Items($seqNum);
+
+C<Items> returns the (number of) items from the specified sequence that
+are part of the current hunk.
+
+If the current hunk contains only insertions, then
+C<< $diff->Items(1) >> will return an empty list (0 in a scalar conext).
+If the current hunk contains only deletions, then C<< $diff->Items(2) >>
+will return an empty list (0 in a scalar conext).
+
+If the hunk contains replacements, then both C<< $diff->Items(1) >> and
+C<< $diff->Items(2) >> will return different, non-empty lists.
+
+Otherwise, the hunk contains identical items and all of the following
+will return the same lists:
+
+ @items = $diff->Items(1);
+ @items = $diff->Items(2);
+ @items = $diff->Same();
+
+=item C<Range>
+
+ $count = $diff->Range( $seqNum );
+ @indices = $diff->Range( $seqNum );
+ @indices = $diff->Range( $seqNum, $base );
+
+C<Range> is like C<Items> except that it returns a list of I<indices> to
+the items rather than the items themselves. By default, the index of
+the first item (in each sequence) is 0 but this can be changed by
+calling the C<Base> method. So, by default, the following two snippets
+return the same lists:
+
+ @list = $diff->Items(2);
+ @list = @seq2[ $diff->Range(2) ];
+
+You can also specify the base to use as the second argument. So the
+following two snippets I<always> return the same lists:
+
+ @list = $diff->Items(1);
+ @list = @seq1[ $diff->Range(1,0) ];
+
+=item C<Base>
+
+ $curBase = $diff->Base();
+ $oldBase = $diff->Base($newBase);
+
+C<Base> sets and/or returns the current base (usually 0 or 1) that is
+used when you request range information. The base defaults to 0 so
+that range information is returned as array indices. You can set the
+base to 1 if you want to report traditional line numbers instead.
+
+=item C<Min>
+
+ $min1 = $diff->Min(1);
+ $min = $diff->Min( $seqNum, $base );
+
+C<Min> returns the first value that C<Range> would return (given the
+same arguments) or returns C<undef> if C<Range> would return an empty
+list.
+
+=item C<Max>
+
+C<Max> returns the last value that C<Range> would return or C<undef>.
+
+=item C<Get>
+
+ ( $n, $x, $r ) = $diff->Get(qw( min1 max1 range1 ));
+ @values = $diff->Get(qw( 0min2 1max2 range2 same base ));
+
+C<Get> returns one or more scalar values. You pass in a list of the
+names of the values you want returned. Each name must match one of the
+following regexes:
+
+ /^(-?\d+)?(min|max)[12]$/i
+ /^(range[12]|same|diff|base)$/i
+
+The 1 or 2 after a name says which sequence you want the information
+for (and where allowed, it is required). The optional number before
+"min" or "max" is the base to use. So the following equalities hold:
+
+ $diff->Get('min1') == $diff->Min(1)
+ $diff->Get('0min2') == $diff->Min(2,0)
+
+Using C<Get> in a scalar context when you've passed in more than one
+name is a fatal error (C<die> is called).
+
+=back
+
+=head2 C<prepare>
+
+Given a reference to a list of items, C<prepare> returns a reference
+to a hash which can be used when comparing this sequence to other
+sequences with C<LCS> or C<LCS_length>.
+
+ $prep = prepare( \@seq1 );
+ for $i ( 0 .. 10_000 )
+ {
+ @lcs = LCS( $prep, $seq[$i] );
+ # do something useful with @lcs
+ }
+
+C<prepare> may be passed an optional third parameter; this is a CODE
+reference to a key generation function. See L</KEY GENERATION
+FUNCTIONS>.
+
+ $prep = prepare( \@seq1, \&keyGen );
+ for $i ( 0 .. 10_000 )
+ {
+ @lcs = LCS( $seq[$i], $prep, \&keyGen );
+ # do something useful with @lcs
+ }
+
+Using C<prepare> provides a performance gain of about 50% when calling LCS
+many times compared with not preparing.
+
+=head2 C<diff>
+
+ @diffs = diff( \@seq1, \@seq2 );
+ $diffs_ref = diff( \@seq1, \@seq2 );
+
+C<diff> computes the smallest set of additions and deletions necessary
+to turn the first sequence into the second, and returns a description
+of these changes. The description is a list of I<hunks>; each hunk
+represents a contiguous section of items which should be added,
+deleted, or replaced. (Hunks containing unchanged items are not
+included.)
+
+The return value of C<diff> is a list of hunks, or, in scalar context, a
+reference to such a list. If there are no differences, the list will be
+empty.
+
+Here is an example. Calling C<diff> for the following two sequences:
+
+ a b c e h j l m n p
+ b c d e f j k l m r s t
+
+would produce the following list:
+
+ (
+ [ [ '-', 0, 'a' ] ],
+
+ [ [ '+', 2, 'd' ] ],
+
+ [ [ '-', 4, 'h' ],
+ [ '+', 4, 'f' ] ],
+
+ [ [ '+', 6, 'k' ] ],
+
+ [ [ '-', 8, 'n' ],
+ [ '-', 9, 'p' ],
+ [ '+', 9, 'r' ],
+ [ '+', 10, 's' ],
+ [ '+', 11, 't' ] ],
+ )
+
+There are five hunks here. The first hunk says that the C<a> at
+position 0 of the first sequence should be deleted (C<->). The second
+hunk says that the C<d> at position 2 of the second sequence should
+be inserted (C<+>). The third hunk says that the C<h> at position 4
+of the first sequence should be removed and replaced with the C<f>
+from position 4 of the second sequence. And so on.
+
+C<diff> may be passed an optional third parameter; this is a CODE
+reference to a key generation function. See L</KEY GENERATION
+FUNCTIONS>.
+
+Additional parameters, if any, will be passed to the key generation
+routine.
+
+=head2 C<sdiff>
+
+ @sdiffs = sdiff( \@seq1, \@seq2 );
+ $sdiffs_ref = sdiff( \@seq1, \@seq2 );
+
+C<sdiff> computes all necessary components to show two sequences
+and their minimized differences side by side, just like the
+Unix-utility I<sdiff> does:
+
+ same same
+ before | after
+ old < -
+ - > new
+
+It returns a list of array refs, each pointing to an array of
+display instructions. In scalar context it returns a reference
+to such a list. If there are no differences, the list will have one
+entry per item, each indicating that the item was unchanged.
+
+Display instructions consist of three elements: A modifier indicator
+(C<+>: Element added, C<->: Element removed, C<u>: Element unmodified,
+C<c>: Element changed) and the value of the old and new elements, to
+be displayed side-by-side.
+
+An C<sdiff> of the following two sequences:
+
+ a b c e h j l m n p
+ b c d e f j k l m r s t
+
+results in
+
+ ( [ '-', 'a', '' ],
+ [ 'u', 'b', 'b' ],
+ [ 'u', 'c', 'c' ],
+ [ '+', '', 'd' ],
+ [ 'u', 'e', 'e' ],
+ [ 'c', 'h', 'f' ],
+ [ 'u', 'j', 'j' ],
+ [ '+', '', 'k' ],
+ [ 'u', 'l', 'l' ],
+ [ 'u', 'm', 'm' ],
+ [ 'c', 'n', 'r' ],
+ [ 'c', 'p', 's' ],
+ [ '+', '', 't' ],
+ )
+
+C<sdiff> may be passed an optional third parameter; this is a CODE
+reference to a key generation function. See L</KEY GENERATION
+FUNCTIONS>.
+
+Additional parameters, if any, will be passed to the key generation
+routine.
+
+=head2 C<compact_diff>
+
+C<compact_diff> is much like C<sdiff> except it returns a much more
+compact description consisting of just one flat list of indices. An
+example helps explain the format:
+
+ my @a = qw( a b c e h j l m n p );
+ my @b = qw( b c d e f j k l m r s t );
+ @cdiff = compact_diff( \@a, \@b );
+ # Returns:
+ # @a @b @a @b
+ # start start values values
+ ( 0, 0, # =
+ 0, 0, # a !
+ 1, 0, # b c = b c
+ 3, 2, # ! d
+ 3, 3, # e = e
+ 4, 4, # f ! h
+ 5, 5, # j = j
+ 6, 6, # ! k
+ 6, 7, # l m = l m
+ 8, 9, # n p ! r s t
+ 10, 12, #
+ );
+
+The 0th, 2nd, 4th, etc. entries are all indices into @seq1 (@a in the
+above example) indicating where a hunk begins. The 1st, 3rd, 5th, etc.
+entries are all indices into @seq2 (@b in the above example) indicating
+where the same hunk begins.
+
+So each pair of indices (except the last pair) describes where a hunk
+begins (in each sequence). Since each hunk must end at the item just
+before the item that starts the next hunk, the next pair of indices can
+be used to determine where the hunk ends.
+
+So, the first 4 entries (0..3) describe the first hunk. Entries 0 and 1
+describe where the first hunk begins (and so are always both 0).
+Entries 2 and 3 describe where the next hunk begins, so subtracting 1
+from each tells us where the first hunk ends. That is, the first hunk
+contains items C<$diff[0]> through C<$diff[2] - 1> of the first sequence
+and contains items C<$diff[1]> through C<$diff[3] - 1> of the second
+sequence.
+
+In other words, the first hunk consists of the following two lists of items:
+
+ # 1st pair 2nd pair
+ # of indices of indices
+ @list1 = @a[ $cdiff[0] .. $cdiff[2]-1 ];
+ @list2 = @b[ $cdiff[1] .. $cdiff[3]-1 ];
+ # Hunk start Hunk end
+
+Note that the hunks will always alternate between those that are part of
+the LCS (those that contain unchanged items) and those that contain
+changes. This means that all we need to be told is whether the first
+hunk is a 'same' or 'diff' hunk and we can determine which of the other
+hunks contain 'same' items or 'diff' items.
+
+By convention, we always make the first hunk contain unchanged items.
+So the 1st, 3rd, 5th, etc. hunks (all odd-numbered hunks if you start
+counting from 1) all contain unchanged items. And the 2nd, 4th, 6th,
+etc. hunks (all even-numbered hunks if you start counting from 1) all
+contain changed items.
+
+Since @a and @b don't begin with the same value, the first hunk in our
+example is empty (otherwise we'd violate the above convention). Note
+that the first 4 index values in our example are all zero. Plug these
+values into our previous code block and we get:
+
+ @hunk1a = @a[ 0 .. 0-1 ];
+ @hunk1b = @b[ 0 .. 0-1 ];
+
+And C<0..-1> returns the empty list.
+
+Move down one pair of indices (2..5) and we get the offset ranges for
+the second hunk, which contains changed items.
+
+Since C<@diff[2..5]> contains (0,0,1,0) in our example, the second hunk
+consists of these two lists of items:
+
+ @hunk2a = @a[ $cdiff[2] .. $cdiff[4]-1 ];
+ @hunk2b = @b[ $cdiff[3] .. $cdiff[5]-1 ];
+ # or
+ @hunk2a = @a[ 0 .. 1-1 ];
+ @hunk2b = @b[ 0 .. 0-1 ];
+ # or
+ @hunk2a = @a[ 0 .. 0 ];
+ @hunk2b = @b[ 0 .. -1 ];
+ # or
+ @hunk2a = ( 'a' );
+ @hunk2b = ( );
+
+That is, we would delete item 0 ('a') from @a.
+
+Since C<@diff[4..7]> contains (1,0,3,2) in our example, the third hunk
+consists of these two lists of items:
+
+ @hunk3a = @a[ $cdiff[4] .. $cdiff[6]-1 ];
+ @hunk3a = @b[ $cdiff[5] .. $cdiff[7]-1 ];
+ # or
+ @hunk3a = @a[ 1 .. 3-1 ];
+ @hunk3a = @b[ 0 .. 2-1 ];
+ # or
+ @hunk3a = @a[ 1 .. 2 ];
+ @hunk3a = @b[ 0 .. 1 ];
+ # or
+ @hunk3a = qw( b c );
+ @hunk3a = qw( b c );
+
+Note that this third hunk contains unchanged items as our convention demands.
+
+You can continue this process until you reach the last two indices,
+which will always be the number of items in each sequence. This is
+required so that subtracting one from each will give you the indices to
+the last items in each sequence.
+
+=head2 C<traverse_sequences>
+
+C<traverse_sequences> used to be the most general facility provided by
+this module (the new OO interface is more powerful and much easier to
+use).
+
+Imagine that there are two arrows. Arrow A points to an element of
+sequence A, and arrow B points to an element of the sequence B.
+Initially, the arrows point to the first elements of the respective
+sequences. C<traverse_sequences> will advance the arrows through the
+sequences one element at a time, calling an appropriate user-specified
+callback function before each advance. It willadvance the arrows in
+such a way that if there are equal elements C<$A[$i]> and C<$B[$j]>
+which are equal and which are part of the LCS, there will be some moment
+during the execution of C<traverse_sequences> when arrow A is pointing
+to C<$A[$i]> and arrow B is pointing to C<$B[$j]>. When this happens,
+C<traverse_sequences> will call the C<MATCH> callback function and then
+it will advance both arrows.
+
+Otherwise, one of the arrows is pointing to an element of its sequence
+that is not part of the LCS. C<traverse_sequences> will advance that
+arrow and will call the C<DISCARD_A> or the C<DISCARD_B> callback,
+depending on which arrow it advanced. If both arrows point to elements
+that are not part of the LCS, then C<traverse_sequences> will advance
+one of them and call the appropriate callback, but it is not specified
+which it will call.
+
+The arguments to C<traverse_sequences> are the two sequences to
+traverse, and a hash which specifies the callback functions, like this:
+
+ traverse_sequences(
+ \@seq1, \@seq2,
+ { MATCH => $callback_1,
+ DISCARD_A => $callback_2,
+ DISCARD_B => $callback_3,
+ }
+ );
+
+Callbacks for MATCH, DISCARD_A, and DISCARD_B are invoked with at least
+the indices of the two arrows as their arguments. They are not expected
+to return any values. If a callback is omitted from the table, it is
+not called.
+
+Callbacks for A_FINISHED and B_FINISHED are invoked with at least the
+corresponding index in A or B.
+
+If arrow A reaches the end of its sequence, before arrow B does,
+C<traverse_sequences> will call the C<A_FINISHED> callback when it
+advances arrow B, if there is such a function; if not it will call
+C<DISCARD_B> instead. Similarly if arrow B finishes first.
+C<traverse_sequences> returns when both arrows are at the ends of their
+respective sequences. It returns true on success and false on failure.
+At present there is no way to fail.
+
+C<traverse_sequences> may be passed an optional fourth parameter; this
+is a CODE reference to a key generation function. See L</KEY GENERATION
+FUNCTIONS>.
+
+Additional parameters, if any, will be passed to the key generation function.
+
+If you want to pass additional parameters to your callbacks, but don't
+need a custom key generation function, you can get the default by
+passing undef:
+
+ traverse_sequences(
+ \@seq1, \@seq2,
+ { MATCH => $callback_1,
+ DISCARD_A => $callback_2,
+ DISCARD_B => $callback_3,
+ },
+ undef, # default key-gen
+ $myArgument1,
+ $myArgument2,
+ $myArgument3,
+ );
+
+C<traverse_sequences> does not have a useful return value; you are
+expected to plug in the appropriate behavior with the callback
+functions.
+
+=head2 C<traverse_balanced>
+
+C<traverse_balanced> is an alternative to C<traverse_sequences>. It
+uses a different algorithm to iterate through the entries in the
+computed LCS. Instead of sticking to one side and showing element changes
+as insertions and deletions only, it will jump back and forth between
+the two sequences and report I<changes> occurring as deletions on one
+side followed immediatly by an insertion on the other side.
+
+In addition to the C<DISCARD_A>, C<DISCARD_B>, and C<MATCH> callbacks
+supported by C<traverse_sequences>, C<traverse_balanced> supports
+a C<CHANGE> callback indicating that one element got C<replaced> by another:
+
+ traverse_balanced(
+ \@seq1, \@seq2,
+ { MATCH => $callback_1,
+ DISCARD_A => $callback_2,
+ DISCARD_B => $callback_3,
+ CHANGE => $callback_4,
+ }
+ );
+
+If no C<CHANGE> callback is specified, C<traverse_balanced>
+will map C<CHANGE> events to C<DISCARD_A> and C<DISCARD_B> actions,
+therefore resulting in a similar behaviour as C<traverse_sequences>
+with different order of events.
+
+C<traverse_balanced> might be a bit slower than C<traverse_sequences>,
+noticable only while processing huge amounts of data.
+
+The C<sdiff> function of this module
+is implemented as call to C<traverse_balanced>.
+
+C<traverse_balanced> does not have a useful return value; you are expected to
+plug in the appropriate behavior with the callback functions.
+
+=head1 KEY GENERATION FUNCTIONS
+
+Most of the functions accept an optional extra parameter. This is a
+CODE reference to a key generating (hashing) function that should return
+a string that uniquely identifies a given element. It should be the
+case that if two elements are to be considered equal, their keys should
+be the same (and the other way around). If no key generation function
+is provided, the key will be the element as a string.
+
+By default, comparisons will use "eq" and elements will be turned into keys
+using the default stringizing operator '""'.
+
+Where this is important is when you're comparing something other than
+strings. If it is the case that you have multiple different objects
+that should be considered to be equal, you should supply a key
+generation function. Otherwise, you have to make sure that your arrays
+contain unique references.
+
+For instance, consider this example:
+
+ package Person;
+
+ sub new
+ {
+ my $package = shift;
+ return bless { name => '', ssn => '', @_ }, $package;
+ }
+
+ sub clone
+ {
+ my $old = shift;
+ my $new = bless { %$old }, ref($old);
+ }
+
+ sub hash
+ {
+ return shift()->{'ssn'};
+ }
+
+ my $person1 = Person->new( name => 'Joe', ssn => '123-45-6789' );
+ my $person2 = Person->new( name => 'Mary', ssn => '123-47-0000' );
+ my $person3 = Person->new( name => 'Pete', ssn => '999-45-2222' );
+ my $person4 = Person->new( name => 'Peggy', ssn => '123-45-9999' );
+ my $person5 = Person->new( name => 'Frank', ssn => '000-45-9999' );
+
+If you did this:
+
+ my $array1 = [ $person1, $person2, $person4 ];
+ my $array2 = [ $person1, $person3, $person4, $person5 ];
+ Algorithm::Diff::diff( $array1, $array2 );
+
+everything would work out OK (each of the objects would be converted
+into a string like "Person=HASH(0x82425b0)" for comparison).
+
+But if you did this:
+
+ my $array1 = [ $person1, $person2, $person4 ];
+ my $array2 = [ $person1, $person3, $person4->clone(), $person5 ];
+ Algorithm::Diff::diff( $array1, $array2 );
+
+$person4 and $person4->clone() (which have the same name and SSN)
+would be seen as different objects. If you wanted them to be considered
+equivalent, you would have to pass in a key generation function:
+
+ my $array1 = [ $person1, $person2, $person4 ];
+ my $array2 = [ $person1, $person3, $person4->clone(), $person5 ];
+ Algorithm::Diff::diff( $array1, $array2, \&Person::hash );
+
+This would use the 'ssn' field in each Person as a comparison key, and
+so would consider $person4 and $person4->clone() as equal.
+
+You may also pass additional parameters to the key generation function
+if you wish.
+
+=head1 ERROR CHECKING
+
+If you pass these routines a non-reference and they expect a reference,
+they will die with a message.
+
+=head1 AUTHOR
+
+This version released by Tye McQueen (http://perlmonks.org/?node=tye).
+
+=head1 LICENSE
+
+Parts Copyright (c) 2000-2004 Ned Konz. All rights reserved.
+Parts by Tye McQueen.
+
+This program is free software; you can redistribute it and/or modify it
+under the same terms as Perl.
+
+=head1 MAILING LIST
+
+Mark-Jason still maintains a mailing list. To join a low-volume mailing
+list for announcements related to diff and Algorithm::Diff, send an
+empty mail message to mjd-perl-diff-request@plover.com.
+
+=head1 CREDITS
+
+Versions through 0.59 (and much of this documentation) were written by:
+
+Mark-Jason Dominus, mjd-perl-diff@plover.com
+
+This version borrows some documentation and routine names from
+Mark-Jason's, but Diff.pm's code was completely replaced.
+
+This code was adapted from the Smalltalk code of Mario Wolczko
+<mario@wolczko.com>, which is available at
+ftp://st.cs.uiuc.edu/pub/Smalltalk/MANCHESTER/manchester/4.0/diff.st
+
+C<sdiff> and C<traverse_balanced> were written by Mike Schilli
+<m@perlmeister.com>.
+
+The algorithm is that described in
+I<A Fast Algorithm for Computing Longest Common Subsequences>,
+CACM, vol.20, no.5, pp.350-353, May 1977, with a few
+minor improvements to improve the speed.
+
+Much work was done by Ned Konz (perl@bike-nomad.com).
+
+The OO interface and some other changes are by Tye McQueen.
+
+=cut
diff --git a/src/tests/Make.tests b/src/tests/Make.tests
new file mode 100644
index 0000000..6880fdf
--- /dev/null
+++ b/src/tests/Make.tests
@@ -0,0 +1,98 @@
+# -*- makefile -*-
+
+include $(patsubst %,$(SRCDIR)/%/Make.tests,$(TEST_SUBDIRS))
+
+PROGS = $(foreach subdir,$(TEST_SUBDIRS),$($(subdir)_PROGS))
+TESTS = $(foreach subdir,$(TEST_SUBDIRS),$($(subdir)_TESTS))
+EXTRA_GRADES = $(foreach subdir,$(TEST_SUBDIRS),$($(subdir)_EXTRA_GRADES))
+
+ALLPUTS = $(addsuffix .allput,$(TESTS) $(EXTRA_GRADES))
+OUTPUTS = $(addsuffix .output,$(TESTS) $(EXTRA_GRADES))
+ERRORS = $(addsuffix .errors,$(TESTS) $(EXTRA_GRADES))
+RESULTS = $(addsuffix .result,$(TESTS) $(EXTRA_GRADES))
+
+ifdef PROGS
+include ../../Makefile.userprog
+endif
+
+TIMEOUT = 60
+
+clean::
+ rm -f $(OUTPUTS) $(ERRORS) $(RESULTS) $(ALLPUTS)
+
+grade:: results
+ $(SRCDIR)/tests/make-grade $(SRCDIR) $< $(GRADING_FILE) | tee $@
+
+# klaar@ida 2011-01-12: new rule to re-run only failed tests
+# use with care, it will mess with the file dates
+recheck:: os.dsk
+# @echo "Cleaning all tests that failed on last run:";
+ @for d in $(TESTS) $(EXTRA_GRADES); do \
+ if echo PASS | cmp -s $$d.result -; then \
+# echo "TOUCH $$d"; \
+ touch $$d.??????; \
+ else \
+# echo "CLEAN $$d"; \
+ rm -f $$d.??????; \
+ fi; \
+ done
+ @$(MAKE) check
+ @touch os.dsk
+ @echo "WARNING: Only failed tests was rerun on new version."
+
+check:: results
+ @cat results
+ @COUNT="`egrep '^(pass|FAIL) ' $< | wc -l | sed 's/[ ]//g;'`"; \
+ FAILURES="`egrep '^FAIL ' $< | wc -l | sed 's/[ ]//g;'`"; \
+ if [ $$FAILURES = 0 ]; then \
+ echo "All $$COUNT tests passed."; \
+ else \
+ echo "$$FAILURES of $$COUNT tests failed."; \
+ exit 1; \
+ fi
+
+results: $(RESULTS)
+ @for d in $(TESTS) $(EXTRA_GRADES); do \
+ if echo PASS | cmp -s $$d.result -; then \
+ echo "pass $$d"; \
+ else \
+ echo "FAIL $$d"; \
+ fi; \
+ done > $@
+
+outputs:: $(OUTPUTS)
+
+$(foreach prog,$(PROGS),$(eval $(prog).output: $(prog)))
+$(foreach test,$(TESTS),$(eval $(test).output: $($(test)_PUTFILES)))
+$(foreach test,$(TESTS),$(eval $(test).output: TEST = $(test)))
+
+# Prevent an environment variable VERBOSE from surprising us.
+VERBOSE =
+
+TESTCMD = pintos -v -k -T $(TIMEOUT)
+TESTCMD += $(SIMULATOR)
+TESTCMD += $(PINTOSOPTS)
+ifeq ($(filter userprog, $(KERNEL_SUBDIRS)), userprog)
+TESTCMD += --fs-disk=$(FSDISK)
+TESTCMD += $(foreach file,$(PUTFILES),-p $(file) -a $(notdir $(file)))
+endif
+ifeq ($(filter vm, $(KERNEL_SUBDIRS)), vm)
+TESTCMD += --swap-disk=4
+endif
+TESTCMD += -- -q
+TESTCMD += $(KERNELFLAGS)
+ifeq ($(filter userprog, $(KERNEL_SUBDIRS)), userprog)
+TESTCMD += -f
+endif
+TESTCMD += $(if $($(TEST)_ARGS),run '$(*F) $($(TEST)_ARGS)',run $(*F))
+TESTCMD += < /dev/null
+TESTCMD += 2> $(TEST).errors $(if $(VERBOSE),|tee,>) $(TEST).allput
+
+# klaar@ida 2011-01-12: added "allput" to be able to strip debug
+# messages before checking
+%.output: os.dsk
+ $(TESTCMD)
+ @grep -v '^# .*$$' $(TEST).allput > $(TEST).output
+
+%.result: %.ck %.output
+ perl -I$(SRCDIR) $< $* $@
diff --git a/src/tests/arc4.c b/src/tests/arc4.c
new file mode 100644
index 0000000..b033cc6
--- /dev/null
+++ b/src/tests/arc4.c
@@ -0,0 +1,53 @@
+#include <stdint.h>
+#include "tests/arc4.h"
+
+/* Swap bytes. */
+static inline void
+swap_byte (uint8_t *a, uint8_t *b)
+{
+ uint8_t t = *a;
+ *a = *b;
+ *b = t;
+}
+
+void
+arc4_init (struct arc4 *arc4, const void *key_, size_t size)
+{
+ const uint8_t *key = key_;
+ size_t key_idx;
+ uint8_t *s;
+ int i, j;
+
+ s = arc4->s;
+ arc4->i = arc4->j = 0;
+ for (i = 0; i < 256; i++)
+ s[i] = i;
+ for (key_idx = 0, i = j = 0; i < 256; i++)
+ {
+ j = (j + s[i] + key[key_idx]) & 255;
+ swap_byte (s + i, s + j);
+ if (++key_idx >= size)
+ key_idx = 0;
+ }
+}
+
+void
+arc4_crypt (struct arc4 *arc4, void *buf_, size_t size)
+{
+ uint8_t *buf = buf_;
+ uint8_t *s;
+ uint8_t i, j;
+
+ s = arc4->s;
+ i = arc4->i;
+ j = arc4->j;
+ while (size-- > 0)
+ {
+ i += 1;
+ j += s[i];
+ swap_byte (s + i, s + j);
+ *buf++ ^= s[(s[i] + s[j]) & 255];
+ }
+ arc4->i = i;
+ arc4->j = j;
+}
diff --git a/src/tests/arc4.h b/src/tests/arc4.h
new file mode 100644
index 0000000..61c533a
--- /dev/null
+++ b/src/tests/arc4.h
@@ -0,0 +1,17 @@
+#ifndef TESTS_ARC4_H
+#define TESTS_ARC4_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+/* Alleged RC4 algorithm encryption state. */
+struct arc4
+ {
+ uint8_t s[256];
+ uint8_t i, j;
+ };
+
+void arc4_init (struct arc4 *, const void *, size_t);
+void arc4_crypt (struct arc4 *, void *, size_t);
+
+#endif /* tests/arc4.h */
diff --git a/src/tests/arc4.pm b/src/tests/arc4.pm
new file mode 100644
index 0000000..df19216
--- /dev/null
+++ b/src/tests/arc4.pm
@@ -0,0 +1,29 @@
+use strict;
+use warnings;
+
+sub arc4_init {
+ my ($key) = @_;
+ my (@s) = 0...255;
+ my ($j) = 0;
+ for my $i (0...255) {
+ $j = ($j + $s[$i] + ord (substr ($key, $i % length ($key), 1))) & 0xff;
+ @s[$i, $j] = @s[$j, $i];
+ }
+ return (0, 0, @s);
+}
+
+sub arc4_crypt {
+ my ($arc4, $buf) = @_;
+ my ($i, $j, @s) = @$arc4;
+ my ($out) = "";
+ for my $c (split (//, $buf)) {
+ $i = ($i + 1) & 0xff;
+ $j = ($j + $s[$i]) & 0xff;
+ @s[$i, $j] = @s[$j, $i];
+ $out .= chr (ord ($c) ^ $s[($s[$i] + $s[$j]) & 0xff]);
+ }
+ @$arc4 = ($i, $j, @s);
+ return $out;
+}
+
+1;
diff --git a/src/tests/cksum.c b/src/tests/cksum.c
new file mode 100644
index 0000000..92a2995
--- /dev/null
+++ b/src/tests/cksum.c
@@ -0,0 +1,92 @@
+/* crctab[] and cksum() are from the `cksum' entry in SUSv3. */
+
+#include <stdint.h>
+#include "tests/cksum.h"
+
+static unsigned long crctab[] = {
+ 0x00000000,
+ 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b,
+ 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6,
+ 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
+ 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac,
+ 0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f,
+ 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a,
+ 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
+ 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58,
+ 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033,
+ 0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe,
+ 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
+ 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4,
+ 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0,
+ 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5,
+ 0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
+ 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07,
+ 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c,
+ 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1,
+ 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
+ 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b,
+ 0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698,
+ 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d,
+ 0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
+ 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f,
+ 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34,
+ 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80,
+ 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
+ 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a,
+ 0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629,
+ 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c,
+ 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
+ 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e,
+ 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65,
+ 0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8,
+ 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
+ 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2,
+ 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71,
+ 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74,
+ 0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
+ 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21,
+ 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a,
+ 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087,
+ 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
+ 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d,
+ 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce,
+ 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb,
+ 0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
+ 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09,
+ 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662,
+ 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf,
+ 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
+};
+
+/* This is the algorithm used by the Posix `cksum' utility. */
+unsigned long
+cksum (const void *b_, size_t n)
+{
+ const unsigned char *b = b_;
+ uint32_t s = 0;
+ size_t i;
+ for (i = n; i > 0; --i)
+ {
+ unsigned char c = *b++;
+ s = (s << 8) ^ crctab[(s >> 24) ^ c];
+ }
+ while (n != 0)
+ {
+ unsigned char c = n;
+ n >>= 8;
+ s = (s << 8) ^ crctab[(s >> 24) ^ c];
+ }
+ return ~s;
+}
+
+#ifdef STANDALONE_TEST
+#include <stdio.h>
+int
+main (void)
+{
+ char buf[65536];
+ int n = fread (buf, 1, sizeof buf, stdin);
+ printf ("%lu\n", cksum (buf, n));
+ return 0;
+}
+#endif
diff --git a/src/tests/cksum.h b/src/tests/cksum.h
new file mode 100644
index 0000000..23a1fe9
--- /dev/null
+++ b/src/tests/cksum.h
@@ -0,0 +1,8 @@
+#ifndef TESTS_CKSUM_H
+#define TESTS_CKSUM_H
+
+#include <stddef.h>
+
+unsigned long cksum(const void *, size_t);
+
+#endif /* tests/cksum.h */
diff --git a/src/tests/cksum.pm b/src/tests/cksum.pm
new file mode 100644
index 0000000..73be5f2
--- /dev/null
+++ b/src/tests/cksum.pm
@@ -0,0 +1,87 @@
+# From the `cksum' entry in SUSv3.
+
+use strict;
+use warnings;
+
+my (@crctab) =
+ (0x00000000,
+ 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b,
+ 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6,
+ 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
+ 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac,
+ 0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f,
+ 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a,
+ 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
+ 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58,
+ 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033,
+ 0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe,
+ 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
+ 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4,
+ 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0,
+ 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5,
+ 0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
+ 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07,
+ 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c,
+ 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1,
+ 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
+ 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b,
+ 0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698,
+ 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d,
+ 0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
+ 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f,
+ 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34,
+ 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80,
+ 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
+ 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a,
+ 0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629,
+ 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c,
+ 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
+ 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e,
+ 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65,
+ 0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8,
+ 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
+ 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2,
+ 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71,
+ 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74,
+ 0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
+ 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21,
+ 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a,
+ 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087,
+ 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
+ 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d,
+ 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce,
+ 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb,
+ 0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
+ 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09,
+ 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662,
+ 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf,
+ 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4);
+
+sub cksum {
+ my ($b) = @_;
+ my ($n) = length ($b);
+ my ($s) = 0;
+ for my $i (0...$n - 1) {
+ my ($c) = ord (substr ($b, $i, 1));
+ $s = ($s << 8) ^ $crctab[($s >> 24) ^ $c];
+ $s &= 0xffff_ffff;
+ }
+ while ($n != 0) {
+ my ($c) = $n & 0xff;
+ $n >>= 8;
+ $s = ($s << 8) ^ $crctab[($s >> 24) ^ $c];
+ $s &= 0xffff_ffff;
+ }
+ return ~$s & 0xffff_ffff;
+}
+
+sub cksum_file {
+ my ($file) = @_;
+ open (FILE, '<', $file) or die "$file: open: $!\n";
+ my ($data);
+ sysread (FILE, $data, -s FILE) == -s FILE or die "$file: read: $!\n";
+ close (FILE);
+ return cksum ($data);
+}
+
+1;
diff --git a/src/tests/filesys/Grading.no-vm b/src/tests/filesys/Grading.no-vm
new file mode 100644
index 0000000..ee98fc1
--- /dev/null
+++ b/src/tests/filesys/Grading.no-vm
@@ -0,0 +1,18 @@
+# Percentage of the testing point total designated for each set of
+# tests.
+
+# This project is primarily about implementing the file system, but
+# all the previous functionality should work too. It's not too easy
+# to screw it up, thus the emphasis.
+
+# 65% for extended file system features.
+30% tests/filesys/extended/Rubric.functionality
+15% tests/filesys/extended/Rubric.robustness
+20% tests/filesys/extended/Rubric.persistence
+
+# 20% to not break the provided file system features.
+20% tests/filesys/base/Rubric
+
+# 15% for the rest.
+10% tests/userprog/Rubric.functionality
+5% tests/userprog/Rubric.robustness
diff --git a/src/tests/filesys/Grading.with-vm b/src/tests/filesys/Grading.with-vm
new file mode 100644
index 0000000..e7c041e
--- /dev/null
+++ b/src/tests/filesys/Grading.with-vm
@@ -0,0 +1,22 @@
+# Percentage of the testing point total designated for each set of
+# tests.
+
+# This project is primarily about implementing the file system, but
+# all the previous functionality should work too. It's not too easy
+# to screw it up, thus the emphasis.
+
+# 65% for extended file system features.
+30% tests/filesys/extended/Rubric.functionality
+15% tests/filesys/extended/Rubric.robustness
+20% tests/filesys/extended/Rubric.persistence
+
+# 20% to not break the provided file system features.
+20% tests/filesys/base/Rubric
+
+# 15% for the rest.
+10% tests/userprog/Rubric.functionality
+5% tests/userprog/Rubric.robustness
+
+# Up to 10% bonus for working VM functionality.
+8% tests/vm/Rubric.functionality
+2% tests/vm/Rubric.robustness
diff --git a/src/tests/filesys/base/Make.tests b/src/tests/filesys/base/Make.tests
new file mode 100644
index 0000000..e475222
--- /dev/null
+++ b/src/tests/filesys/base/Make.tests
@@ -0,0 +1,18 @@
+# -*- makefile -*-
+
+tests/filesys/base_TESTS = $(addprefix tests/filesys/base/,lg-create \
+lg-full lg-random lg-seq-block lg-seq-random sm-create sm-full \
+sm-random sm-seq-block sm-seq-random syn-read syn-remove syn-write)
+
+tests/filesys/base_PROGS = $(tests/filesys/base_TESTS) $(addprefix \
+tests/filesys/base/,child-syn-read child-syn-wrt)
+
+$(foreach prog,$(tests/filesys/base_PROGS), \
+ $(eval $(prog)_SRC += $(prog).c tests/lib.c tests/filesys/seq-test.c))
+$(foreach prog,$(tests/filesys/base_TESTS), \
+ $(eval $(prog)_SRC += tests/main.c))
+
+tests/filesys/base/syn-read_PUTFILES = tests/filesys/base/child-syn-read
+tests/filesys/base/syn-write_PUTFILES = tests/filesys/base/child-syn-wrt
+
+tests/filesys/base/syn-read.output: TIMEOUT = 300
diff --git a/src/tests/filesys/base/Rubric b/src/tests/filesys/base/Rubric
new file mode 100644
index 0000000..49a9d15
--- /dev/null
+++ b/src/tests/filesys/base/Rubric
@@ -0,0 +1,19 @@
+Functionality of base file system:
+- Test basic support for small files.
+1 sm-create
+2 sm-full
+2 sm-random
+2 sm-seq-block
+3 sm-seq-random
+
+- Test basic support for large files.
+1 lg-create
+2 lg-full
+2 lg-random
+2 lg-seq-block
+3 lg-seq-random
+
+- Test synchronized multiprogram access to files.
+4 syn-read
+4 syn-write
+2 syn-remove
diff --git a/src/tests/filesys/base/child-syn-read.c b/src/tests/filesys/base/child-syn-read.c
new file mode 100644
index 0000000..77a5e26
--- /dev/null
+++ b/src/tests/filesys/base/child-syn-read.c
@@ -0,0 +1,44 @@
+/* Child process for syn-read test.
+ Reads the contents of a test file a byte at a time, in the
+ hope that this will take long enough that we can get a
+ significant amount of contention in the kernel file system
+ code. */
+
+#include <random.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/filesys/base/syn-read.h"
+
+const char *test_name = "child-syn-read";
+
+static char buf[BUF_SIZE];
+
+int
+main (int argc, const char *argv[])
+{
+ int child_idx;
+ int fd;
+ size_t i;
+
+ quiet = true;
+
+ CHECK (argc == 2, "argc must be 2, actually %d", argc);
+ child_idx = atoi (argv[1]);
+
+ random_init (0);
+ random_bytes (buf, sizeof buf);
+
+ CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name);
+ for (i = 0; i < sizeof buf; i++)
+ {
+ char c;
+ CHECK (read (fd, &c, 1) > 0, "read \"%s\"", file_name);
+ compare_bytes (&c, buf + i, 1, i, file_name);
+ }
+ close (fd);
+
+ return child_idx;
+}
+
diff --git a/src/tests/filesys/base/child-syn-wrt.c b/src/tests/filesys/base/child-syn-wrt.c
new file mode 100644
index 0000000..1b52584
--- /dev/null
+++ b/src/tests/filesys/base/child-syn-wrt.c
@@ -0,0 +1,35 @@
+/* Child process for syn-read test.
+ Writes into part of a test file. Other processes will be
+ writing into other parts at the same time. */
+
+#include <random.h>
+#include <stdlib.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/filesys/base/syn-write.h"
+
+char buf[BUF_SIZE];
+
+int
+main (int argc, char *argv[])
+{
+ int child_idx;
+ int fd;
+
+ quiet = true;
+
+ CHECK (argc == 2, "argc must be 2, actually %d", argc);
+ child_idx = atoi (argv[1]);
+
+ random_init (0);
+ random_bytes (buf, sizeof buf);
+
+ CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name);
+ seek (fd, CHUNK_SIZE * child_idx);
+ CHECK (write (fd, buf + CHUNK_SIZE * child_idx, CHUNK_SIZE) > 0,
+ "write \"%s\"", file_name);
+ msg ("close \"%s\"", file_name);
+ close (fd);
+
+ return child_idx;
+}
diff --git a/src/tests/filesys/base/full.inc b/src/tests/filesys/base/full.inc
new file mode 100644
index 0000000..38a0396
--- /dev/null
+++ b/src/tests/filesys/base/full.inc
@@ -0,0 +1,20 @@
+/* -*- c -*- */
+
+#include "tests/filesys/seq-test.h"
+#include "tests/main.h"
+
+static char buf[TEST_SIZE];
+
+static size_t
+return_test_size (void)
+{
+ return TEST_SIZE;
+}
+
+void
+test_main (void)
+{
+ seq_test ("quux",
+ buf, sizeof buf, sizeof buf,
+ return_test_size, NULL);
+}
diff --git a/src/tests/filesys/base/lg-create.c b/src/tests/filesys/base/lg-create.c
new file mode 100644
index 0000000..5c45eee
--- /dev/null
+++ b/src/tests/filesys/base/lg-create.c
@@ -0,0 +1,5 @@
+/* Tests that create properly zeros out the contents of a fairly
+ large file. */
+
+#define TEST_SIZE 75678
+#include "tests/filesys/create.inc"
diff --git a/src/tests/filesys/base/lg-create.ck b/src/tests/filesys/base/lg-create.ck
new file mode 100644
index 0000000..86b2c51
--- /dev/null
+++ b/src/tests/filesys/base/lg-create.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(lg-create) begin
+(lg-create) create "blargle"
+(lg-create) open "blargle" for verification
+(lg-create) verified contents of "blargle"
+(lg-create) close "blargle"
+(lg-create) end
+EOF
+pass;
diff --git a/src/tests/filesys/base/lg-full.c b/src/tests/filesys/base/lg-full.c
new file mode 100644
index 0000000..5f7234d
--- /dev/null
+++ b/src/tests/filesys/base/lg-full.c
@@ -0,0 +1,6 @@
+/* Writes out the contents of a fairly large file all at once,
+ and then reads it back to make sure that it was written
+ properly. */
+
+#define TEST_SIZE 75678
+#include "tests/filesys/base/full.inc"
diff --git a/src/tests/filesys/base/lg-full.ck b/src/tests/filesys/base/lg-full.ck
new file mode 100644
index 0000000..ee6c7f9
--- /dev/null
+++ b/src/tests/filesys/base/lg-full.ck
@@ -0,0 +1,16 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(lg-full) begin
+(lg-full) create "quux"
+(lg-full) open "quux"
+(lg-full) writing "quux"
+(lg-full) close "quux"
+(lg-full) open "quux" for verification
+(lg-full) verified contents of "quux"
+(lg-full) close "quux"
+(lg-full) end
+EOF
+pass;
diff --git a/src/tests/filesys/base/lg-random.c b/src/tests/filesys/base/lg-random.c
new file mode 100644
index 0000000..b6f8873
--- /dev/null
+++ b/src/tests/filesys/base/lg-random.c
@@ -0,0 +1,7 @@
+/* Writes out the content of a fairly large file in random order,
+ then reads it back in random order to verify that it was
+ written properly. */
+
+#define BLOCK_SIZE 512
+#define TEST_SIZE (512 * 150)
+#include "tests/filesys/base/random.inc"
diff --git a/src/tests/filesys/base/lg-random.ck b/src/tests/filesys/base/lg-random.ck
new file mode 100644
index 0000000..dd9f1dd
--- /dev/null
+++ b/src/tests/filesys/base/lg-random.ck
@@ -0,0 +1,14 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(lg-random) begin
+(lg-random) create "bazzle"
+(lg-random) open "bazzle"
+(lg-random) write "bazzle" in random order
+(lg-random) read "bazzle" in random order
+(lg-random) close "bazzle"
+(lg-random) end
+EOF
+pass;
diff --git a/src/tests/filesys/base/lg-seq-block.c b/src/tests/filesys/base/lg-seq-block.c
new file mode 100644
index 0000000..580c30b
--- /dev/null
+++ b/src/tests/filesys/base/lg-seq-block.c
@@ -0,0 +1,7 @@
+/* Writes out a fairly large file sequentially, one fixed-size
+ block at a time, then reads it back to verify that it was
+ written properly. */
+
+#define TEST_SIZE 75678
+#define BLOCK_SIZE 513
+#include "tests/filesys/base/seq-block.inc"
diff --git a/src/tests/filesys/base/lg-seq-block.ck b/src/tests/filesys/base/lg-seq-block.ck
new file mode 100644
index 0000000..b789081
--- /dev/null
+++ b/src/tests/filesys/base/lg-seq-block.ck
@@ -0,0 +1,16 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(lg-seq-block) begin
+(lg-seq-block) create "noodle"
+(lg-seq-block) open "noodle"
+(lg-seq-block) writing "noodle"
+(lg-seq-block) close "noodle"
+(lg-seq-block) open "noodle" for verification
+(lg-seq-block) verified contents of "noodle"
+(lg-seq-block) close "noodle"
+(lg-seq-block) end
+EOF
+pass;
diff --git a/src/tests/filesys/base/lg-seq-random.c b/src/tests/filesys/base/lg-seq-random.c
new file mode 100644
index 0000000..fbb6bba
--- /dev/null
+++ b/src/tests/filesys/base/lg-seq-random.c
@@ -0,0 +1,6 @@
+/* Writes out a fairly large file sequentially, one random-sized
+ block at a time, then reads it back to verify that it was
+ written properly. */
+
+#define TEST_SIZE 75678
+#include "tests/filesys/base/seq-random.inc"
diff --git a/src/tests/filesys/base/lg-seq-random.ck b/src/tests/filesys/base/lg-seq-random.ck
new file mode 100644
index 0000000..6b2dc82
--- /dev/null
+++ b/src/tests/filesys/base/lg-seq-random.ck
@@ -0,0 +1,16 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(lg-seq-random) begin
+(lg-seq-random) create "nibble"
+(lg-seq-random) open "nibble"
+(lg-seq-random) writing "nibble"
+(lg-seq-random) close "nibble"
+(lg-seq-random) open "nibble" for verification
+(lg-seq-random) verified contents of "nibble"
+(lg-seq-random) close "nibble"
+(lg-seq-random) end
+EOF
+pass;
diff --git a/src/tests/filesys/base/random.inc b/src/tests/filesys/base/random.inc
new file mode 100644
index 0000000..eeeea68
--- /dev/null
+++ b/src/tests/filesys/base/random.inc
@@ -0,0 +1,59 @@
+/* -*- c -*- */
+
+#include <random.h>
+#include <stdio.h>
+#include <string.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#if TEST_SIZE % BLOCK_SIZE != 0
+#error TEST_SIZE must be a multiple of BLOCK_SIZE
+#endif
+
+#define BLOCK_CNT (TEST_SIZE / BLOCK_SIZE)
+
+char buf[TEST_SIZE];
+int order[BLOCK_CNT];
+
+void
+test_main (void)
+{
+ const char *file_name = "bazzle";
+ int fd;
+ size_t i;
+
+ random_init (57);
+ random_bytes (buf, sizeof buf);
+
+ for (i = 0; i < BLOCK_CNT; i++)
+ order[i] = i;
+
+ CHECK (create (file_name, TEST_SIZE), "create \"%s\"", file_name);
+ CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name);
+
+ msg ("write \"%s\" in random order", file_name);
+ shuffle (order, BLOCK_CNT, sizeof *order);
+ for (i = 0; i < BLOCK_CNT; i++)
+ {
+ size_t ofs = BLOCK_SIZE * order[i];
+ seek (fd, ofs);
+ if (write (fd, buf + ofs, BLOCK_SIZE) != BLOCK_SIZE)
+ fail ("write %d bytes at offset %zu failed", (int) BLOCK_SIZE, ofs);
+ }
+
+ msg ("read \"%s\" in random order", file_name);
+ shuffle (order, BLOCK_CNT, sizeof *order);
+ for (i = 0; i < BLOCK_CNT; i++)
+ {
+ char block[BLOCK_SIZE];
+ size_t ofs = BLOCK_SIZE * order[i];
+ seek (fd, ofs);
+ if (read (fd, block, BLOCK_SIZE) != BLOCK_SIZE)
+ fail ("read %d bytes at offset %zu failed", (int) BLOCK_SIZE, ofs);
+ compare_bytes (block, buf + ofs, BLOCK_SIZE, ofs, file_name);
+ }
+
+ msg ("close \"%s\"", file_name);
+ close (fd);
+}
diff --git a/src/tests/filesys/base/seq-block.inc b/src/tests/filesys/base/seq-block.inc
new file mode 100644
index 0000000..d4c1f57
--- /dev/null
+++ b/src/tests/filesys/base/seq-block.inc
@@ -0,0 +1,20 @@
+/* -*- c -*- */
+
+#include "tests/filesys/seq-test.h"
+#include "tests/main.h"
+
+static char buf[TEST_SIZE];
+
+static size_t
+return_block_size (void)
+{
+ return BLOCK_SIZE;
+}
+
+void
+test_main (void)
+{
+ seq_test ("noodle",
+ buf, sizeof buf, sizeof buf,
+ return_block_size, NULL);
+}
diff --git a/src/tests/filesys/base/seq-random.inc b/src/tests/filesys/base/seq-random.inc
new file mode 100644
index 0000000..a4da4c5
--- /dev/null
+++ b/src/tests/filesys/base/seq-random.inc
@@ -0,0 +1,22 @@
+/* -*- c -*- */
+
+#include <random.h>
+#include "tests/filesys/seq-test.h"
+#include "tests/main.h"
+
+static char buf[TEST_SIZE];
+
+static size_t
+return_random (void)
+{
+ return random_ulong () % 1031 + 1;
+}
+
+void
+test_main (void)
+{
+ random_init (-1);
+ seq_test ("nibble",
+ buf, sizeof buf, sizeof buf,
+ return_random, NULL);
+}
diff --git a/src/tests/filesys/base/sm-create.c b/src/tests/filesys/base/sm-create.c
new file mode 100644
index 0000000..6b97ac1
--- /dev/null
+++ b/src/tests/filesys/base/sm-create.c
@@ -0,0 +1,5 @@
+/* Tests that create properly zeros out the contents of a fairly
+ small file. */
+
+#define TEST_SIZE 5678
+#include "tests/filesys/create.inc"
diff --git a/src/tests/filesys/base/sm-create.ck b/src/tests/filesys/base/sm-create.ck
new file mode 100644
index 0000000..8ca80dc
--- /dev/null
+++ b/src/tests/filesys/base/sm-create.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(sm-create) begin
+(sm-create) create "blargle"
+(sm-create) open "blargle" for verification
+(sm-create) verified contents of "blargle"
+(sm-create) close "blargle"
+(sm-create) end
+EOF
+pass;
diff --git a/src/tests/filesys/base/sm-full.c b/src/tests/filesys/base/sm-full.c
new file mode 100644
index 0000000..23ff3d4
--- /dev/null
+++ b/src/tests/filesys/base/sm-full.c
@@ -0,0 +1,6 @@
+/* Writes out the contents of a fairly small file all at once,
+ and then reads it back to make sure that it was written
+ properly. */
+
+#define TEST_SIZE 5678
+#include "tests/filesys/base/full.inc"
diff --git a/src/tests/filesys/base/sm-full.ck b/src/tests/filesys/base/sm-full.ck
new file mode 100644
index 0000000..2e0eb36
--- /dev/null
+++ b/src/tests/filesys/base/sm-full.ck
@@ -0,0 +1,16 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(sm-full) begin
+(sm-full) create "quux"
+(sm-full) open "quux"
+(sm-full) writing "quux"
+(sm-full) close "quux"
+(sm-full) open "quux" for verification
+(sm-full) verified contents of "quux"
+(sm-full) close "quux"
+(sm-full) end
+EOF
+pass;
diff --git a/src/tests/filesys/base/sm-random.c b/src/tests/filesys/base/sm-random.c
new file mode 100644
index 0000000..42d670f
--- /dev/null
+++ b/src/tests/filesys/base/sm-random.c
@@ -0,0 +1,7 @@
+/* Writes out the content of a fairly small file in random order,
+ then reads it back in random order to verify that it was
+ written properly. */
+
+#define BLOCK_SIZE 13
+#define TEST_SIZE (13 * 123)
+#include "tests/filesys/base/random.inc"
diff --git a/src/tests/filesys/base/sm-random.ck b/src/tests/filesys/base/sm-random.ck
new file mode 100644
index 0000000..bda049d
--- /dev/null
+++ b/src/tests/filesys/base/sm-random.ck
@@ -0,0 +1,14 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(sm-random) begin
+(sm-random) create "bazzle"
+(sm-random) open "bazzle"
+(sm-random) write "bazzle" in random order
+(sm-random) read "bazzle" in random order
+(sm-random) close "bazzle"
+(sm-random) end
+EOF
+pass;
diff --git a/src/tests/filesys/base/sm-seq-block.c b/src/tests/filesys/base/sm-seq-block.c
new file mode 100644
index 0000000..e368327
--- /dev/null
+++ b/src/tests/filesys/base/sm-seq-block.c
@@ -0,0 +1,7 @@
+/* Writes out a fairly small file sequentially, one fixed-size
+ block at a time, then reads it back to verify that it was
+ written properly. */
+
+#define TEST_SIZE 5678
+#define BLOCK_SIZE 513
+#include "tests/filesys/base/seq-block.inc"
diff --git a/src/tests/filesys/base/sm-seq-block.ck b/src/tests/filesys/base/sm-seq-block.ck
new file mode 100644
index 0000000..0e2939d
--- /dev/null
+++ b/src/tests/filesys/base/sm-seq-block.ck
@@ -0,0 +1,16 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(sm-seq-block) begin
+(sm-seq-block) create "noodle"
+(sm-seq-block) open "noodle"
+(sm-seq-block) writing "noodle"
+(sm-seq-block) close "noodle"
+(sm-seq-block) open "noodle" for verification
+(sm-seq-block) verified contents of "noodle"
+(sm-seq-block) close "noodle"
+(sm-seq-block) end
+EOF
+pass;
diff --git a/src/tests/filesys/base/sm-seq-random.c b/src/tests/filesys/base/sm-seq-random.c
new file mode 100644
index 0000000..89e5b71
--- /dev/null
+++ b/src/tests/filesys/base/sm-seq-random.c
@@ -0,0 +1,6 @@
+/* Writes out a fairly large file sequentially, one random-sized
+ block at a time, then reads it back to verify that it was
+ written properly. */
+
+#define TEST_SIZE 5678
+#include "tests/filesys/base/seq-random.inc"
diff --git a/src/tests/filesys/base/sm-seq-random.ck b/src/tests/filesys/base/sm-seq-random.ck
new file mode 100644
index 0000000..2fb368b
--- /dev/null
+++ b/src/tests/filesys/base/sm-seq-random.ck
@@ -0,0 +1,16 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(sm-seq-random) begin
+(sm-seq-random) create "nibble"
+(sm-seq-random) open "nibble"
+(sm-seq-random) writing "nibble"
+(sm-seq-random) close "nibble"
+(sm-seq-random) open "nibble" for verification
+(sm-seq-random) verified contents of "nibble"
+(sm-seq-random) close "nibble"
+(sm-seq-random) end
+EOF
+pass;
diff --git a/src/tests/filesys/base/syn-read.c b/src/tests/filesys/base/syn-read.c
new file mode 100644
index 0000000..7c36a42
--- /dev/null
+++ b/src/tests/filesys/base/syn-read.c
@@ -0,0 +1,31 @@
+/* Spawns 10 child processes, all of which read from the same
+ file and make sure that the contents are what they should
+ be. */
+
+#include <random.h>
+#include <stdio.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+#include "tests/filesys/base/syn-read.h"
+
+static char buf[BUF_SIZE];
+
+#define CHILD_CNT 10
+
+void
+test_main (void)
+{
+ pid_t children[CHILD_CNT];
+ int fd;
+
+ CHECK (create (file_name, sizeof buf), "create \"%s\"", file_name);
+ CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name);
+ random_bytes (buf, sizeof buf);
+ CHECK (write (fd, buf, sizeof buf) > 0, "write \"%s\"", file_name);
+ msg ("close \"%s\"", file_name);
+ close (fd);
+
+ exec_children ("child-syn-read", children, CHILD_CNT);
+ wait_children (children, CHILD_CNT);
+}
diff --git a/src/tests/filesys/base/syn-read.ck b/src/tests/filesys/base/syn-read.ck
new file mode 100644
index 0000000..e2f68e8
--- /dev/null
+++ b/src/tests/filesys/base/syn-read.ck
@@ -0,0 +1,33 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(syn-read) begin
+(syn-read) create "data"
+(syn-read) open "data"
+(syn-read) write "data"
+(syn-read) close "data"
+(syn-read) exec child 1 of 10: "child-syn-read 0"
+(syn-read) exec child 2 of 10: "child-syn-read 1"
+(syn-read) exec child 3 of 10: "child-syn-read 2"
+(syn-read) exec child 4 of 10: "child-syn-read 3"
+(syn-read) exec child 5 of 10: "child-syn-read 4"
+(syn-read) exec child 6 of 10: "child-syn-read 5"
+(syn-read) exec child 7 of 10: "child-syn-read 6"
+(syn-read) exec child 8 of 10: "child-syn-read 7"
+(syn-read) exec child 9 of 10: "child-syn-read 8"
+(syn-read) exec child 10 of 10: "child-syn-read 9"
+(syn-read) wait for child 1 of 10 returned 0 (expected 0)
+(syn-read) wait for child 2 of 10 returned 1 (expected 1)
+(syn-read) wait for child 3 of 10 returned 2 (expected 2)
+(syn-read) wait for child 4 of 10 returned 3 (expected 3)
+(syn-read) wait for child 5 of 10 returned 4 (expected 4)
+(syn-read) wait for child 6 of 10 returned 5 (expected 5)
+(syn-read) wait for child 7 of 10 returned 6 (expected 6)
+(syn-read) wait for child 8 of 10 returned 7 (expected 7)
+(syn-read) wait for child 9 of 10 returned 8 (expected 8)
+(syn-read) wait for child 10 of 10 returned 9 (expected 9)
+(syn-read) end
+EOF
+pass;
diff --git a/src/tests/filesys/base/syn-read.h b/src/tests/filesys/base/syn-read.h
new file mode 100644
index 0000000..bff8082
--- /dev/null
+++ b/src/tests/filesys/base/syn-read.h
@@ -0,0 +1,7 @@
+#ifndef TESTS_FILESYS_BASE_SYN_READ_H
+#define TESTS_FILESYS_BASE_SYN_READ_H
+
+#define BUF_SIZE 1024
+static const char file_name[] = "data";
+
+#endif /* tests/filesys/base/syn-read.h */
diff --git a/src/tests/filesys/base/syn-remove.c b/src/tests/filesys/base/syn-remove.c
new file mode 100644
index 0000000..c9ba110
--- /dev/null
+++ b/src/tests/filesys/base/syn-remove.c
@@ -0,0 +1,30 @@
+/* Verifies that a deleted file may still be written to and read
+ from. */
+
+#include <random.h>
+#include <string.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+char buf1[1234];
+char buf2[1234];
+
+void
+test_main (void)
+{
+ const char *file_name = "deleteme";
+ int fd;
+
+ CHECK (create (file_name, sizeof buf1), "create \"%s\"", file_name);
+ CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name);
+ CHECK (remove (file_name), "remove \"%s\"", file_name);
+ random_bytes (buf1, sizeof buf1);
+ CHECK (write (fd, buf1, sizeof buf1) > 0, "write \"%s\"", file_name);
+ msg ("seek \"%s\" to 0", file_name);
+ seek (fd, 0);
+ CHECK (read (fd, buf2, sizeof buf2) > 0, "read \"%s\"", file_name);
+ compare_bytes (buf2, buf1, sizeof buf1, 0, file_name);
+ msg ("close \"%s\"", file_name);
+ close (fd);
+}
diff --git a/src/tests/filesys/base/syn-remove.ck b/src/tests/filesys/base/syn-remove.ck
new file mode 100644
index 0000000..16ff11e
--- /dev/null
+++ b/src/tests/filesys/base/syn-remove.ck
@@ -0,0 +1,16 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(syn-remove) begin
+(syn-remove) create "deleteme"
+(syn-remove) open "deleteme"
+(syn-remove) remove "deleteme"
+(syn-remove) write "deleteme"
+(syn-remove) seek "deleteme" to 0
+(syn-remove) read "deleteme"
+(syn-remove) close "deleteme"
+(syn-remove) end
+EOF
+pass;
diff --git a/src/tests/filesys/base/syn-write.c b/src/tests/filesys/base/syn-write.c
new file mode 100644
index 0000000..1439862
--- /dev/null
+++ b/src/tests/filesys/base/syn-write.c
@@ -0,0 +1,31 @@
+/* Spawns several child processes to write out different parts of
+ the contents of a file and waits for them to finish. Then
+ reads back the file and verifies its contents. */
+
+#include <random.h>
+#include <stdio.h>
+#include <string.h>
+#include <syscall.h>
+#include "tests/filesys/base/syn-write.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+char buf1[BUF_SIZE];
+char buf2[BUF_SIZE];
+
+void
+test_main (void)
+{
+ pid_t children[CHILD_CNT];
+ int fd;
+
+ CHECK (create (file_name, sizeof buf1), "create \"%s\"", file_name);
+
+ exec_children ("child-syn-wrt", children, CHILD_CNT);
+ wait_children (children, CHILD_CNT);
+
+ CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name);
+ CHECK (read (fd, buf1, sizeof buf1) > 0, "read \"%s\"", file_name);
+ random_bytes (buf2, sizeof buf2);
+ compare_bytes (buf1, buf2, sizeof buf1, 0, file_name);
+}
diff --git a/src/tests/filesys/base/syn-write.ck b/src/tests/filesys/base/syn-write.ck
new file mode 100644
index 0000000..629a7a2
--- /dev/null
+++ b/src/tests/filesys/base/syn-write.ck
@@ -0,0 +1,32 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(syn-write) begin
+(syn-write) create "stuff"
+(syn-write) exec child 1 of 10: "child-syn-wrt 0"
+(syn-write) exec child 2 of 10: "child-syn-wrt 1"
+(syn-write) exec child 3 of 10: "child-syn-wrt 2"
+(syn-write) exec child 4 of 10: "child-syn-wrt 3"
+(syn-write) exec child 5 of 10: "child-syn-wrt 4"
+(syn-write) exec child 6 of 10: "child-syn-wrt 5"
+(syn-write) exec child 7 of 10: "child-syn-wrt 6"
+(syn-write) exec child 8 of 10: "child-syn-wrt 7"
+(syn-write) exec child 9 of 10: "child-syn-wrt 8"
+(syn-write) exec child 10 of 10: "child-syn-wrt 9"
+(syn-write) wait for child 1 of 10 returned 0 (expected 0)
+(syn-write) wait for child 2 of 10 returned 1 (expected 1)
+(syn-write) wait for child 3 of 10 returned 2 (expected 2)
+(syn-write) wait for child 4 of 10 returned 3 (expected 3)
+(syn-write) wait for child 5 of 10 returned 4 (expected 4)
+(syn-write) wait for child 6 of 10 returned 5 (expected 5)
+(syn-write) wait for child 7 of 10 returned 6 (expected 6)
+(syn-write) wait for child 8 of 10 returned 7 (expected 7)
+(syn-write) wait for child 9 of 10 returned 8 (expected 8)
+(syn-write) wait for child 10 of 10 returned 9 (expected 9)
+(syn-write) open "stuff"
+(syn-write) read "stuff"
+(syn-write) end
+EOF
+pass;
diff --git a/src/tests/filesys/base/syn-write.h b/src/tests/filesys/base/syn-write.h
new file mode 100644
index 0000000..07a6d5a
--- /dev/null
+++ b/src/tests/filesys/base/syn-write.h
@@ -0,0 +1,9 @@
+#ifndef TESTS_FILESYS_BASE_SYN_WRITE_H
+#define TESTS_FILESYS_BASE_SYN_WRITE_H
+
+#define CHILD_CNT 10
+#define CHUNK_SIZE 512
+#define BUF_SIZE (CHILD_CNT * CHUNK_SIZE)
+static const char file_name[] = "stuff";
+
+#endif /* tests/filesys/base/syn-write.h */
diff --git a/src/tests/filesys/create.inc b/src/tests/filesys/create.inc
new file mode 100644
index 0000000..4baf771
--- /dev/null
+++ b/src/tests/filesys/create.inc
@@ -0,0 +1,15 @@
+/* -*- c -*- */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+static char buf[TEST_SIZE];
+
+void
+test_main (void)
+{
+ const char *file_name = "blargle";
+ CHECK (create (file_name, TEST_SIZE), "create \"%s\"", file_name);
+ check_file (file_name, buf, TEST_SIZE);
+}
diff --git a/src/tests/filesys/extended/Make.tests b/src/tests/filesys/extended/Make.tests
new file mode 100644
index 0000000..75a872b
--- /dev/null
+++ b/src/tests/filesys/extended/Make.tests
@@ -0,0 +1,61 @@
+# -*- makefile -*-
+
+raw_tests = dir-empty-name dir-mk-tree dir-mkdir dir-open \
+dir-over-file dir-rm-cwd dir-rm-parent dir-rm-root dir-rm-tree \
+dir-rmdir dir-under-file dir-vine grow-create grow-dir-lg \
+grow-file-size grow-root-lg grow-root-sm grow-seq-lg grow-seq-sm \
+grow-sparse grow-tell grow-two-files syn-rw
+
+tests/filesys/extended_TESTS = $(patsubst %,tests/filesys/extended/%,$(raw_tests))
+tests/filesys/extended_EXTRA_GRADES = $(patsubst %,tests/filesys/extended/%-persistence,$(raw_tests))
+
+tests/filesys/extended_PROGS = $(tests/filesys/extended_TESTS) \
+tests/filesys/extended/child-syn-rw tests/filesys/extended/tar
+
+$(foreach prog,$(tests/filesys/extended_PROGS), \
+ $(eval $(prog)_SRC += $(prog).c tests/lib.c tests/filesys/seq-test.c))
+$(foreach prog,$(tests/filesys/extended_TESTS), \
+ $(eval $(prog)_SRC += tests/main.c))
+$(foreach prog,$(tests/filesys/extended_TESTS), \
+ $(eval $(prog)_PUTFILES += tests/filesys/extended/tar))
+# The version of GNU make 3.80 on vine barfs if this is split at
+# the last comma.
+$(foreach test,$(tests/filesys/extended_TESTS),$(eval $(test).output: FSDISK = tmp.dsk))
+
+tests/filesys/extended/dir-mk-tree_SRC += tests/filesys/extended/mk-tree.c
+tests/filesys/extended/dir-rm-tree_SRC += tests/filesys/extended/mk-tree.c
+
+tests/filesys/extended/syn-rw_PUTFILES += tests/filesys/extended/child-syn-rw
+
+tests/filesys/extended/dir-vine.output: TIMEOUT = 150
+
+GETTIMEOUT = 60
+
+GETCMD = pintos -v -k -T $(GETTIMEOUT)
+GETCMD += $(PINTOSOPTS)
+GETCMD += $(SIMULATOR)
+GETCMD += --fs-disk=$(FSDISK)
+GETCMD += -g fs.tar -a $(TEST).tar
+ifeq ($(filter vm, $(KERNEL_SUBDIRS)), vm)
+GETCMD += --swap-disk=4
+endif
+GETCMD += -- -q
+GETCMD += $(KERNELFLAGS)
+GETCMD += run 'tar fs.tar /'
+GETCMD += < /dev/null
+GETCMD += 2> $(TEST)-persistence.errors $(if $(VERBOSE),|tee,>) $(TEST)-persistence.output
+
+tests/filesys/extended/%.output: os.dsk
+ rm -f tmp.dsk
+ pintos-mkdisk tmp.dsk 2
+ $(TESTCMD)
+ $(GETCMD)
+ rm -f tmp.dsk
+$(foreach raw_test,$(raw_tests),$(eval tests/filesys/extended/$(raw_test)-persistence.output: tests/filesys/extended/$(raw_test).output))
+$(foreach raw_test,$(raw_tests),$(eval tests/filesys/extended/$(raw_test)-persistence.result: tests/filesys/extended/$(raw_test).result))
+
+TARS = $(addsuffix .tar,$(tests/filesys/extended_TESTS))
+
+clean::
+ rm -f $(TARS)
+ rm -f tests/filesys/extended/can-rmdir-cwd
diff --git a/src/tests/filesys/extended/Rubric.functionality b/src/tests/filesys/extended/Rubric.functionality
new file mode 100644
index 0000000..91ed6f0
--- /dev/null
+++ b/src/tests/filesys/extended/Rubric.functionality
@@ -0,0 +1,26 @@
+Functionality of extended file system:
+- Test directory support.
+1 dir-mkdir
+3 dir-mk-tree
+
+1 dir-rmdir
+3 dir-rm-tree
+
+5 dir-vine
+
+- Test file growth.
+1 grow-create
+1 grow-seq-sm
+3 grow-seq-lg
+3 grow-sparse
+3 grow-two-files
+1 grow-tell
+1 grow-file-size
+
+- Test directory growth.
+1 grow-dir-lg
+1 grow-root-sm
+1 grow-root-lg
+
+- Test writing from multiple processes.
+5 syn-rw
diff --git a/src/tests/filesys/extended/Rubric.persistence b/src/tests/filesys/extended/Rubric.persistence
new file mode 100644
index 0000000..405620a
--- /dev/null
+++ b/src/tests/filesys/extended/Rubric.persistence
@@ -0,0 +1,24 @@
+Persistence of file system:
+1 dir-empty-name-persistence
+1 dir-mk-tree-persistence
+1 dir-mkdir-persistence
+1 dir-open-persistence
+1 dir-over-file-persistence
+1 dir-rm-cwd-persistence
+1 dir-rm-parent-persistence
+1 dir-rm-root-persistence
+1 dir-rm-tree-persistence
+1 dir-rmdir-persistence
+1 dir-under-file-persistence
+1 dir-vine-persistence
+1 grow-create-persistence
+1 grow-dir-lg-persistence
+1 grow-file-size-persistence
+1 grow-root-lg-persistence
+1 grow-root-sm-persistence
+1 grow-seq-lg-persistence
+1 grow-seq-sm-persistence
+1 grow-sparse-persistence
+1 grow-tell-persistence
+1 grow-two-files-persistence
+1 syn-rw-persistence
diff --git a/src/tests/filesys/extended/Rubric.robustness b/src/tests/filesys/extended/Rubric.robustness
new file mode 100644
index 0000000..fb9f32f
--- /dev/null
+++ b/src/tests/filesys/extended/Rubric.robustness
@@ -0,0 +1,9 @@
+Robustness of file system:
+1 dir-empty-name
+1 dir-open
+1 dir-over-file
+1 dir-under-file
+
+3 dir-rm-cwd
+2 dir-rm-parent
+1 dir-rm-root
diff --git a/src/tests/filesys/extended/child-syn-rw.c b/src/tests/filesys/extended/child-syn-rw.c
new file mode 100644
index 0000000..0e2217d
--- /dev/null
+++ b/src/tests/filesys/extended/child-syn-rw.c
@@ -0,0 +1,53 @@
+/* Child process for syn-rw.
+ Reads from a file created by our parent process, which is
+ growing it. We loop until we've read the whole file
+ successfully. Many iterations through the loop will return 0
+ bytes, because the file has not grown in the meantime. That
+ is, we are "busy waiting" for the file to grow.
+ (This test could be improved by adding a "yield" system call
+ and calling yield whenever we receive a 0-byte read.) */
+
+#include <random.h>
+#include <stdlib.h>
+#include <syscall.h>
+#include "tests/filesys/extended/syn-rw.h"
+#include "tests/lib.h"
+
+const char *test_name = "child-syn-rw";
+
+static char buf1[BUF_SIZE];
+static char buf2[BUF_SIZE];
+
+int
+main (int argc, const char *argv[])
+{
+ int child_idx;
+ int fd;
+ size_t ofs;
+
+ quiet = true;
+
+ CHECK (argc == 2, "argc must be 2, actually %d", argc);
+ child_idx = atoi (argv[1]);
+
+ random_init (0);
+ random_bytes (buf1, sizeof buf1);
+
+ CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name);
+ ofs = 0;
+ while (ofs < sizeof buf2)
+ {
+ int bytes_read = read (fd, buf2 + ofs, sizeof buf2 - ofs);
+ CHECK (bytes_read >= -1 && bytes_read <= (int) (sizeof buf2 - ofs),
+ "%zu-byte read on \"%s\" returned invalid value of %d",
+ sizeof buf2 - ofs, file_name, bytes_read);
+ if (bytes_read > 0)
+ {
+ compare_bytes (buf2 + ofs, buf1 + ofs, bytes_read, ofs, file_name);
+ ofs += bytes_read;
+ }
+ }
+ close (fd);
+
+ return child_idx;
+}
diff --git a/src/tests/filesys/extended/dir-empty-name-persistence.ck b/src/tests/filesys/extended/dir-empty-name-persistence.ck
new file mode 100644
index 0000000..562c451
--- /dev/null
+++ b/src/tests/filesys/extended/dir-empty-name-persistence.ck
@@ -0,0 +1,6 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_archive ({});
+pass;
diff --git a/src/tests/filesys/extended/dir-empty-name.c b/src/tests/filesys/extended/dir-empty-name.c
new file mode 100644
index 0000000..c4859d2
--- /dev/null
+++ b/src/tests/filesys/extended/dir-empty-name.c
@@ -0,0 +1,12 @@
+/* Tries to create a directory named as the empty string,
+ which must return failure. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ CHECK (!mkdir (""), "mkdir \"\" (must return false)");
+}
diff --git a/src/tests/filesys/extended/dir-empty-name.ck b/src/tests/filesys/extended/dir-empty-name.ck
new file mode 100644
index 0000000..d6c5621
--- /dev/null
+++ b/src/tests/filesys/extended/dir-empty-name.ck
@@ -0,0 +1,10 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(dir-empty-name) begin
+(dir-empty-name) mkdir "" (must return false)
+(dir-empty-name) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/dir-mk-tree-persistence.ck b/src/tests/filesys/extended/dir-mk-tree-persistence.ck
new file mode 100644
index 0000000..fb16afd
--- /dev/null
+++ b/src/tests/filesys/extended/dir-mk-tree-persistence.ck
@@ -0,0 +1,16 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+my ($tree);
+for my $a (0...3) {
+ for my $b (0...2) {
+ for my $c (0...2) {
+ for my $d (0...3) {
+ $tree->{$a}{$b}{$c}{$d} = [''];
+ }
+ }
+ }
+}
+check_archive ($tree);
+pass;
diff --git a/src/tests/filesys/extended/dir-mk-tree.c b/src/tests/filesys/extended/dir-mk-tree.c
new file mode 100644
index 0000000..a714ff3
--- /dev/null
+++ b/src/tests/filesys/extended/dir-mk-tree.c
@@ -0,0 +1,12 @@
+/* Creates directories /0/0/0 through /3/2/2 and creates files in
+ the leaf directories. */
+
+#include "tests/filesys/extended/mk-tree.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ make_tree (4, 3, 3, 4);
+}
+
diff --git a/src/tests/filesys/extended/dir-mk-tree.ck b/src/tests/filesys/extended/dir-mk-tree.ck
new file mode 100644
index 0000000..a8507e2
--- /dev/null
+++ b/src/tests/filesys/extended/dir-mk-tree.ck
@@ -0,0 +1,12 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(dir-mk-tree) begin
+(dir-mk-tree) creating /0/0/0/0 through /3/2/2/3...
+(dir-mk-tree) open "/0/2/0/3"
+(dir-mk-tree) close "/0/2/0/3"
+(dir-mk-tree) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/dir-mkdir-persistence.ck b/src/tests/filesys/extended/dir-mkdir-persistence.ck
new file mode 100644
index 0000000..7682900
--- /dev/null
+++ b/src/tests/filesys/extended/dir-mkdir-persistence.ck
@@ -0,0 +1,6 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_archive ({'a' => {'b' => ["\0" x 512]}});
+pass;
diff --git a/src/tests/filesys/extended/dir-mkdir.c b/src/tests/filesys/extended/dir-mkdir.c
new file mode 100644
index 0000000..994f41c
--- /dev/null
+++ b/src/tests/filesys/extended/dir-mkdir.c
@@ -0,0 +1,15 @@
+/* Tests mkdir(). */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ CHECK (mkdir ("a"), "mkdir \"a\"");
+ CHECK (create ("a/b", 512), "create \"a/b\"");
+ CHECK (chdir ("a"), "chdir \"a\"");
+ CHECK (open ("b") > 1, "open \"b\"");
+}
+
diff --git a/src/tests/filesys/extended/dir-mkdir.ck b/src/tests/filesys/extended/dir-mkdir.ck
new file mode 100644
index 0000000..4644f80
--- /dev/null
+++ b/src/tests/filesys/extended/dir-mkdir.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(dir-mkdir) begin
+(dir-mkdir) mkdir "a"
+(dir-mkdir) create "a/b"
+(dir-mkdir) chdir "a"
+(dir-mkdir) open "b"
+(dir-mkdir) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/dir-open-persistence.ck b/src/tests/filesys/extended/dir-open-persistence.ck
new file mode 100644
index 0000000..26ff2f1
--- /dev/null
+++ b/src/tests/filesys/extended/dir-open-persistence.ck
@@ -0,0 +1,6 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_archive ({"xyzzy" => {}});
+pass;
diff --git a/src/tests/filesys/extended/dir-open.c b/src/tests/filesys/extended/dir-open.c
new file mode 100644
index 0000000..29d18b8
--- /dev/null
+++ b/src/tests/filesys/extended/dir-open.c
@@ -0,0 +1,21 @@
+/* Opens a directory, then tries to write to it, which must
+ fail. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ int fd;
+ int retval;
+
+ CHECK (mkdir ("xyzzy"), "mkdir \"xyzzy\"");
+ CHECK ((fd = open ("xyzzy")) > 1, "open \"xyzzy\"");
+
+ msg ("write \"xyzzy\"");
+ retval = write (fd, "foobar", 6);
+ CHECK (retval == -1,
+ "write \"xyzzy\" (must return -1, actually %d)", retval);
+}
diff --git a/src/tests/filesys/extended/dir-open.ck b/src/tests/filesys/extended/dir-open.ck
new file mode 100644
index 0000000..fccc563
--- /dev/null
+++ b/src/tests/filesys/extended/dir-open.ck
@@ -0,0 +1,20 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(dir-open) begin
+(dir-open) mkdir "xyzzy"
+(dir-open) open "xyzzy"
+(dir-open) write "xyzzy"
+(dir-open) write "xyzzy" (must return -1, actually -1)
+(dir-open) end
+dir-open: exit(0)
+EOF
+(dir-open) begin
+(dir-open) mkdir "xyzzy"
+(dir-open) open "xyzzy"
+(dir-open) write "xyzzy"
+dir-open: exit(-1)
+EOF
+pass;
diff --git a/src/tests/filesys/extended/dir-over-file-persistence.ck b/src/tests/filesys/extended/dir-over-file-persistence.ck
new file mode 100644
index 0000000..56b4ed2
--- /dev/null
+++ b/src/tests/filesys/extended/dir-over-file-persistence.ck
@@ -0,0 +1,6 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_archive ({"abc" => {}});
+pass;
diff --git a/src/tests/filesys/extended/dir-over-file.c b/src/tests/filesys/extended/dir-over-file.c
new file mode 100644
index 0000000..cdd2c62
--- /dev/null
+++ b/src/tests/filesys/extended/dir-over-file.c
@@ -0,0 +1,13 @@
+/* Tries to create a file with the same name as an existing
+ directory, which must return failure. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ CHECK (mkdir ("abc"), "mkdir \"abc\"");
+ CHECK (!create ("abc", 0), "create \"abc\" (must return false)");
+}
diff --git a/src/tests/filesys/extended/dir-over-file.ck b/src/tests/filesys/extended/dir-over-file.ck
new file mode 100644
index 0000000..aae1c1e
--- /dev/null
+++ b/src/tests/filesys/extended/dir-over-file.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(dir-over-file) begin
+(dir-over-file) mkdir "abc"
+(dir-over-file) create "abc" (must return false)
+(dir-over-file) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/dir-rm-cwd-persistence.ck b/src/tests/filesys/extended/dir-rm-cwd-persistence.ck
new file mode 100644
index 0000000..7533570
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rm-cwd-persistence.ck
@@ -0,0 +1,8 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+my ($cwd_removable) = read_text_file ("tests/filesys/extended/can-rmdir-cwd");
+$cwd_removable eq 'YES' || $cwd_removable eq 'NO' or die;
+check_archive ($cwd_removable eq 'YES' ? {} : {"a" => {}});
+pass;
diff --git a/src/tests/filesys/extended/dir-rm-cwd.c b/src/tests/filesys/extended/dir-rm-cwd.c
new file mode 100644
index 0000000..78e13de
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rm-cwd.c
@@ -0,0 +1,75 @@
+/* Tries to remove the current directory, which may succeed or
+ fail. The requirements in each case are different; refer to
+ the assignment for details. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+static int
+wrap_open (const char *name)
+{
+ static int fds[8], fd_cnt;
+ int fd, i;
+
+ CHECK ((fd = open (name)) > 1, "open \"%s\"", name);
+ for (i = 0; i < fd_cnt; i++)
+ if (fds[i] == fd)
+ fail ("fd returned is not unique");
+ fds[fd_cnt++] = fd;
+ return fd;
+}
+
+void
+test_main (void)
+{
+ int root_fd, a_fd0;
+ char name[READDIR_MAX_LEN + 1];
+
+ root_fd = wrap_open ("/");
+ CHECK (mkdir ("a"), "mkdir \"a\"");
+
+ a_fd0 = wrap_open ("/a");
+ CHECK (!readdir (a_fd0, name), "verify \"/a\" is empty");
+ CHECK (inumber (root_fd) != inumber (a_fd0),
+ "\"/\" and \"/a\" must have different inumbers");
+
+ CHECK (chdir ("a"), "chdir \"a\"");
+
+ msg ("try to remove \"/a\"");
+ if (remove ("/a"))
+ {
+ msg ("remove successful");
+
+ CHECK (open ("/a") == -1, "open \"/a\" (must fail)");
+ CHECK (open (".") == -1, "open \".\" (must fail)");
+ CHECK (open ("..") == -1, "open \"..\" (must fail)");
+ CHECK (!create ("x", 512), "create \"x\" (must fail)");
+ }
+ else
+ {
+ int a_fd1, a_fd2, a_fd3;
+
+ msg ("remove failed");
+
+ CHECK (!remove ("../a"), "try to remove \"../a\" (must fail)");
+ CHECK (!remove (".././a"), "try to remove \".././a\" (must fail)");
+ CHECK (!remove ("/./a"), "try to remove \"/./a\" (must fail)");
+
+ a_fd1 = wrap_open ("/a");
+ a_fd2 = wrap_open (".");
+ CHECK (inumber (a_fd1) == inumber (a_fd2),
+ "\"/a\" and \".\" must have same inumber");
+ CHECK (inumber (root_fd) != inumber (a_fd1),
+ "\"/\" and \"/a\" must have different inumbers");
+
+ CHECK (chdir ("/a"), "chdir \"/a\"");
+ a_fd3 = wrap_open (".");
+ CHECK (inumber (a_fd3) == inumber (a_fd1),
+ "\".\" must have same inumber as before");
+
+ CHECK (chdir ("/"), "chdir \"/\"");
+ CHECK (!remove ("a"), "try to remove \"a\" (must fail: still open)");
+ }
+ CHECK (!readdir (a_fd0, name), "verify \"/a\" is empty");
+}
diff --git a/src/tests/filesys/extended/dir-rm-cwd.ck b/src/tests/filesys/extended/dir-rm-cwd.ck
new file mode 100644
index 0000000..6fa4739
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rm-cwd.ck
@@ -0,0 +1,51 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+my ($cwd_removable) = check_expected (IGNORE_EXIT_CODES => 1,
+ {NO => <<'EOF', YES => <<'EOF'});
+(dir-rm-cwd) begin
+(dir-rm-cwd) open "/"
+(dir-rm-cwd) mkdir "a"
+(dir-rm-cwd) open "/a"
+(dir-rm-cwd) verify "/a" is empty
+(dir-rm-cwd) "/" and "/a" must have different inumbers
+(dir-rm-cwd) chdir "a"
+(dir-rm-cwd) try to remove "/a"
+(dir-rm-cwd) remove failed
+(dir-rm-cwd) try to remove "../a" (must fail)
+(dir-rm-cwd) try to remove ".././a" (must fail)
+(dir-rm-cwd) try to remove "/./a" (must fail)
+(dir-rm-cwd) open "/a"
+(dir-rm-cwd) open "."
+(dir-rm-cwd) "/a" and "." must have same inumber
+(dir-rm-cwd) "/" and "/a" must have different inumbers
+(dir-rm-cwd) chdir "/a"
+(dir-rm-cwd) open "."
+(dir-rm-cwd) "." must have same inumber as before
+(dir-rm-cwd) chdir "/"
+(dir-rm-cwd) try to remove "a" (must fail: still open)
+(dir-rm-cwd) verify "/a" is empty
+(dir-rm-cwd) end
+EOF
+(dir-rm-cwd) begin
+(dir-rm-cwd) open "/"
+(dir-rm-cwd) mkdir "a"
+(dir-rm-cwd) open "/a"
+(dir-rm-cwd) verify "/a" is empty
+(dir-rm-cwd) "/" and "/a" must have different inumbers
+(dir-rm-cwd) chdir "a"
+(dir-rm-cwd) try to remove "/a"
+(dir-rm-cwd) remove successful
+(dir-rm-cwd) open "/a" (must fail)
+(dir-rm-cwd) open "." (must fail)
+(dir-rm-cwd) open ".." (must fail)
+(dir-rm-cwd) create "x" (must fail)
+(dir-rm-cwd) verify "/a" is empty
+(dir-rm-cwd) end
+EOF
+open (CAN_RMDIR_CWD, ">tests/filesys/extended/can-rmdir-cwd")
+ or die "tests/filesys/extended/can-rmdir-cwd: create: $!\n";
+print CAN_RMDIR_CWD "$cwd_removable";
+close (CAN_RMDIR_CWD);
+pass;
diff --git a/src/tests/filesys/extended/dir-rm-parent-persistence.ck b/src/tests/filesys/extended/dir-rm-parent-persistence.ck
new file mode 100644
index 0000000..f30b04a
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rm-parent-persistence.ck
@@ -0,0 +1,6 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_archive ({"a" => {"b" => {}}});
+pass;
diff --git a/src/tests/filesys/extended/dir-rm-parent.c b/src/tests/filesys/extended/dir-rm-parent.c
new file mode 100644
index 0000000..eb43f5b
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rm-parent.c
@@ -0,0 +1,16 @@
+/* Tries to remove a parent of the current directory. This must
+ fail, because that directory is non-empty. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ CHECK (mkdir ("a"), "mkdir \"a\"");
+ CHECK (chdir ("a"), "chdir \"a\"");
+ CHECK (mkdir ("b"), "mkdir \"b\"");
+ CHECK (chdir ("b"), "chdir \"b\"");
+ CHECK (!remove ("/a"), "remove \"/a\" (must fail)");
+}
diff --git a/src/tests/filesys/extended/dir-rm-parent.ck b/src/tests/filesys/extended/dir-rm-parent.ck
new file mode 100644
index 0000000..9fea8f2
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rm-parent.ck
@@ -0,0 +1,14 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(dir-rm-parent) begin
+(dir-rm-parent) mkdir "a"
+(dir-rm-parent) chdir "a"
+(dir-rm-parent) mkdir "b"
+(dir-rm-parent) chdir "b"
+(dir-rm-parent) remove "/a" (must fail)
+(dir-rm-parent) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/dir-rm-root-persistence.ck b/src/tests/filesys/extended/dir-rm-root-persistence.ck
new file mode 100644
index 0000000..6315107
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rm-root-persistence.ck
@@ -0,0 +1,6 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_archive ({"a" => ["\0" x 243]});
+pass;
diff --git a/src/tests/filesys/extended/dir-rm-root.c b/src/tests/filesys/extended/dir-rm-root.c
new file mode 100644
index 0000000..c47f1eb
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rm-root.c
@@ -0,0 +1,13 @@
+/* Try to remove the root directory.
+ This must fail. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ CHECK (!remove ("/"), "remove \"/\" (must fail)");
+ CHECK (create ("/a", 243), "create \"/a\"");
+}
diff --git a/src/tests/filesys/extended/dir-rm-root.ck b/src/tests/filesys/extended/dir-rm-root.ck
new file mode 100644
index 0000000..8a69ff3
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rm-root.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(dir-rm-root) begin
+(dir-rm-root) remove "/" (must fail)
+(dir-rm-root) create "/a"
+(dir-rm-root) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/dir-rm-tree-persistence.ck b/src/tests/filesys/extended/dir-rm-tree-persistence.ck
new file mode 100644
index 0000000..562c451
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rm-tree-persistence.ck
@@ -0,0 +1,6 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_archive ({});
+pass;
diff --git a/src/tests/filesys/extended/dir-rm-tree.c b/src/tests/filesys/extended/dir-rm-tree.c
new file mode 100644
index 0000000..bab41a6
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rm-tree.c
@@ -0,0 +1,62 @@
+/* Creates directories /0/0/0 through /3/2/2 and files in the
+ leaf directories, then removes them. */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <syscall.h>
+#include "tests/filesys/extended/mk-tree.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+static void remove_tree (int at, int bt, int ct, int dt);
+
+void
+test_main (void)
+{
+ make_tree (4, 3, 3, 4);
+ remove_tree (4, 3, 3, 4);
+}
+
+static void do_remove (const char *format, ...) PRINTF_FORMAT (1, 2);
+
+static void
+remove_tree (int at, int bt, int ct, int dt)
+{
+ char try[128];
+ int a, b, c, d;
+
+ msg ("removing /0/0/0/0 through /%d/%d/%d/%d...",
+ at - 1, bt - 1, ct - 1, dt - 1);
+ quiet = true;
+ for (a = 0; a < at; a++)
+ {
+ for (b = 0; b < bt; b++)
+ {
+ for (c = 0; c < ct; c++)
+ {
+ for (d = 0; d < dt; d++)
+ do_remove ("/%d/%d/%d/%d", a, b, c, d);
+ do_remove ("/%d/%d/%d", a, b, c);
+ }
+ do_remove ("/%d/%d", a, b);
+ }
+ do_remove ("/%d", a);
+ }
+ quiet = false;
+
+ snprintf (try, sizeof (try), "/%d/%d/%d/%d", at - 1, 0, ct - 1, 0);
+ CHECK (open (try) == -1, "open \"%s\" (must return -1)", try);
+}
+
+static void
+do_remove (const char *format, ...)
+{
+ char name[128];
+ va_list args;
+
+ va_start (args, format);
+ vsnprintf (name, sizeof name, format, args);
+ va_end (args);
+
+ CHECK (remove (name), "remove \"%s\"", name);
+}
diff --git a/src/tests/filesys/extended/dir-rm-tree.ck b/src/tests/filesys/extended/dir-rm-tree.ck
new file mode 100644
index 0000000..587b493
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rm-tree.ck
@@ -0,0 +1,14 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(dir-rm-tree) begin
+(dir-rm-tree) creating /0/0/0/0 through /3/2/2/3...
+(dir-rm-tree) open "/0/2/0/3"
+(dir-rm-tree) close "/0/2/0/3"
+(dir-rm-tree) removing /0/0/0/0 through /3/2/2/3...
+(dir-rm-tree) open "/3/0/2/0" (must return -1)
+(dir-rm-tree) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/dir-rmdir-persistence.ck b/src/tests/filesys/extended/dir-rmdir-persistence.ck
new file mode 100644
index 0000000..562c451
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rmdir-persistence.ck
@@ -0,0 +1,6 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_archive ({});
+pass;
diff --git a/src/tests/filesys/extended/dir-rmdir.c b/src/tests/filesys/extended/dir-rmdir.c
new file mode 100644
index 0000000..b3cbc6f
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rmdir.c
@@ -0,0 +1,14 @@
+/* Creates and removes a directory, then makes sure that it's
+ really gone. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ CHECK (mkdir ("a"), "mkdir \"a\"");
+ CHECK (remove ("a"), "rmdir \"a\"");
+ CHECK (!chdir ("a"), "chdir \"a\" (must return false)");
+}
diff --git a/src/tests/filesys/extended/dir-rmdir.ck b/src/tests/filesys/extended/dir-rmdir.ck
new file mode 100644
index 0000000..e0d8922
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rmdir.ck
@@ -0,0 +1,12 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(dir-rmdir) begin
+(dir-rmdir) mkdir "a"
+(dir-rmdir) rmdir "a"
+(dir-rmdir) chdir "a" (must return false)
+(dir-rmdir) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/dir-under-file-persistence.ck b/src/tests/filesys/extended/dir-under-file-persistence.ck
new file mode 100644
index 0000000..67ca528
--- /dev/null
+++ b/src/tests/filesys/extended/dir-under-file-persistence.ck
@@ -0,0 +1,6 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_archive ({"abc" => ['']});
+pass;
diff --git a/src/tests/filesys/extended/dir-under-file.c b/src/tests/filesys/extended/dir-under-file.c
new file mode 100644
index 0000000..973a8b1
--- /dev/null
+++ b/src/tests/filesys/extended/dir-under-file.c
@@ -0,0 +1,13 @@
+/* Tries to create a directory with the same name as an existing
+ file, which must return failure. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ CHECK (create ("abc", 0), "create \"abc\"");
+ CHECK (!mkdir ("abc"), "mkdir \"abc\" (must return false)");
+}
diff --git a/src/tests/filesys/extended/dir-under-file.ck b/src/tests/filesys/extended/dir-under-file.ck
new file mode 100644
index 0000000..cce23b4
--- /dev/null
+++ b/src/tests/filesys/extended/dir-under-file.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(dir-under-file) begin
+(dir-under-file) create "abc"
+(dir-under-file) mkdir "abc" (must return false)
+(dir-under-file) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/dir-vine-persistence.ck b/src/tests/filesys/extended/dir-vine-persistence.ck
new file mode 100644
index 0000000..698ef01
--- /dev/null
+++ b/src/tests/filesys/extended/dir-vine-persistence.ck
@@ -0,0 +1,37 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+# The archive should look like this:
+#
+# 40642 dir-vine
+# 42479 tar
+# 0 start
+# 11 start/file0
+# 0 start/dir0
+# 11 start/dir0/file1
+# 0 start/dir0/dir1
+# 11 start/dir0/dir1/file2
+# 0 start/dir0/dir1/dir2
+# 11 start/dir0/dir1/dir2/file3
+# 0 start/dir0/dir1/dir2/dir3
+# 11 start/dir0/dir1/dir2/dir3/file4
+# 0 start/dir0/dir1/dir2/dir3/dir4
+# 11 start/dir0/dir1/dir2/dir3/dir4/file5
+# 0 start/dir0/dir1/dir2/dir3/dir4/dir5
+# 11 start/dir0/dir1/dir2/dir3/dir4/dir5/file6
+# 0 start/dir0/dir1/dir2/dir3/dir4/dir5/dir6
+# 11 start/dir0/dir1/dir2/dir3/dir4/dir5/dir6/file7
+# 0 start/dir0/dir1/dir2/dir3/dir4/dir5/dir6/dir7
+# 11 start/dir0/dir1/dir2/dir3/dir4/dir5/dir6/dir7/file8
+# 0 start/dir0/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8
+# 11 start/dir0/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/file9
+# 0 start/dir0/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/dir9
+my ($dir) = {};
+my ($root) = {"start" => $dir};
+for (my ($i) = 0; $i < 10; $i++) {
+ $dir->{"file$i"} = ["contents $i\n"];
+ $dir = $dir->{"dir$i"} = {};
+}
+check_archive ($root);
+pass;
diff --git a/src/tests/filesys/extended/dir-vine.c b/src/tests/filesys/extended/dir-vine.c
new file mode 100644
index 0000000..8a31c38
--- /dev/null
+++ b/src/tests/filesys/extended/dir-vine.c
@@ -0,0 +1,85 @@
+/* Create a very deep "vine" of directories: /dir0/dir1/dir2/...
+ and an ordinary file in each of them, until we fill up the
+ disk.
+
+ Then delete most of them, for two reasons. First, "tar"
+ limits file names to 100 characters (which could be extended
+ to 256 without much trouble). Second, a full disk has no room
+ for the tar archive. */
+
+#include <string.h>
+#include <stdio.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ int i;
+
+ msg ("creating many levels of files and directories...");
+ quiet = true;
+ CHECK (mkdir ("start"), "mkdir \"start\"");
+ CHECK (chdir ("start"), "chdir \"start\"");
+ for (i = 0; ; i++)
+ {
+ char name[3][READDIR_MAX_LEN + 1];
+ char file_name[16], dir_name[16];
+ char contents[128];
+ int fd;
+
+ /* Create file. */
+ snprintf (file_name, sizeof file_name, "file%d", i);
+ if (!create (file_name, 0))
+ break;
+ CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name);
+ snprintf (contents, sizeof contents, "contents %d\n", i);
+ if (write (fd, contents, strlen (contents)) != (int) strlen (contents))
+ {
+ CHECK (remove (file_name), "remove \"%s\"", file_name);
+ close (fd);
+ break;
+ }
+ close (fd);
+
+ /* Create directory. */
+ snprintf (dir_name, sizeof dir_name, "dir%d", i);
+ if (!mkdir (dir_name))
+ {
+ CHECK (remove (file_name), "remove \"%s\"", file_name);
+ break;
+ }
+
+ /* Check for file and directory. */
+ CHECK ((fd = open (".")) > 1, "open \".\"");
+ CHECK (readdir (fd, name[0]), "readdir \".\"");
+ CHECK (readdir (fd, name[1]), "readdir \".\"");
+ CHECK (!readdir (fd, name[2]), "readdir \".\" (should fail)");
+ CHECK ((!strcmp (name[0], dir_name) && !strcmp (name[1], file_name))
+ || (!strcmp (name[1], dir_name) && !strcmp (name[0], file_name)),
+ "names should be \"%s\" and \"%s\", "
+ "actually \"%s\" and \"%s\"",
+ file_name, dir_name, name[0], name[1]);
+ close (fd);
+
+ /* Descend into directory. */
+ CHECK (chdir (dir_name), "chdir \"%s\"", dir_name);
+ }
+ CHECK (i > 200, "created files and directories only to level %d", i);
+ quiet = false;
+
+ msg ("removing all but top 10 levels of files and directories...");
+ quiet = true;
+ while (i-- > 10)
+ {
+ char file_name[16], dir_name[16];
+
+ snprintf (file_name, sizeof file_name, "file%d", i);
+ snprintf (dir_name, sizeof dir_name, "dir%d", i);
+ CHECK (chdir (".."), "chdir \"..\"");
+ CHECK (remove (dir_name), "remove \"%s\"", dir_name);
+ CHECK (remove (file_name), "remove \"%s\"", file_name);
+ }
+ quiet = false;
+}
diff --git a/src/tests/filesys/extended/dir-vine.ck b/src/tests/filesys/extended/dir-vine.ck
new file mode 100644
index 0000000..db452b0
--- /dev/null
+++ b/src/tests/filesys/extended/dir-vine.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(dir-vine) begin
+(dir-vine) creating many levels of files and directories...
+(dir-vine) removing all but top 10 levels of files and directories...
+(dir-vine) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/grow-create-persistence.ck b/src/tests/filesys/extended/grow-create-persistence.ck
new file mode 100644
index 0000000..bbcb24f
--- /dev/null
+++ b/src/tests/filesys/extended/grow-create-persistence.ck
@@ -0,0 +1,6 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_archive ({"blargle" => ['']});
+pass;
diff --git a/src/tests/filesys/extended/grow-create.c b/src/tests/filesys/extended/grow-create.c
new file mode 100644
index 0000000..9ccc4ea
--- /dev/null
+++ b/src/tests/filesys/extended/grow-create.c
@@ -0,0 +1,4 @@
+/* Create a file of size 0. */
+
+#define TEST_SIZE 0
+#include "tests/filesys/create.inc"
diff --git a/src/tests/filesys/extended/grow-create.ck b/src/tests/filesys/extended/grow-create.ck
new file mode 100644
index 0000000..b2e69d1
--- /dev/null
+++ b/src/tests/filesys/extended/grow-create.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(grow-create) begin
+(grow-create) create "blargle"
+(grow-create) open "blargle" for verification
+(grow-create) verified contents of "blargle"
+(grow-create) close "blargle"
+(grow-create) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/grow-dir-lg-persistence.ck b/src/tests/filesys/extended/grow-dir-lg-persistence.ck
new file mode 100644
index 0000000..989a322
--- /dev/null
+++ b/src/tests/filesys/extended/grow-dir-lg-persistence.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+my ($fs);
+$fs->{'x'}{"file$_"} = [random_bytes (512)] foreach 0...49;
+check_archive ($fs);
+pass;
diff --git a/src/tests/filesys/extended/grow-dir-lg.c b/src/tests/filesys/extended/grow-dir-lg.c
new file mode 100644
index 0000000..20a194b
--- /dev/null
+++ b/src/tests/filesys/extended/grow-dir-lg.c
@@ -0,0 +1,6 @@
+/* Creates a directory,
+ then creates 50 files in that directory. */
+
+#define FILE_CNT 50
+#define DIRECTORY "/x"
+#include "tests/filesys/extended/grow-dir.inc"
diff --git a/src/tests/filesys/extended/grow-dir-lg.ck b/src/tests/filesys/extended/grow-dir-lg.ck
new file mode 100644
index 0000000..ec58bd3
--- /dev/null
+++ b/src/tests/filesys/extended/grow-dir-lg.ck
@@ -0,0 +1,61 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(grow-dir-lg) begin
+(grow-dir-lg) mkdir /x
+(grow-dir-lg) creating and checking "/x/file0"
+(grow-dir-lg) creating and checking "/x/file1"
+(grow-dir-lg) creating and checking "/x/file2"
+(grow-dir-lg) creating and checking "/x/file3"
+(grow-dir-lg) creating and checking "/x/file4"
+(grow-dir-lg) creating and checking "/x/file5"
+(grow-dir-lg) creating and checking "/x/file6"
+(grow-dir-lg) creating and checking "/x/file7"
+(grow-dir-lg) creating and checking "/x/file8"
+(grow-dir-lg) creating and checking "/x/file9"
+(grow-dir-lg) creating and checking "/x/file10"
+(grow-dir-lg) creating and checking "/x/file11"
+(grow-dir-lg) creating and checking "/x/file12"
+(grow-dir-lg) creating and checking "/x/file13"
+(grow-dir-lg) creating and checking "/x/file14"
+(grow-dir-lg) creating and checking "/x/file15"
+(grow-dir-lg) creating and checking "/x/file16"
+(grow-dir-lg) creating and checking "/x/file17"
+(grow-dir-lg) creating and checking "/x/file18"
+(grow-dir-lg) creating and checking "/x/file19"
+(grow-dir-lg) creating and checking "/x/file20"
+(grow-dir-lg) creating and checking "/x/file21"
+(grow-dir-lg) creating and checking "/x/file22"
+(grow-dir-lg) creating and checking "/x/file23"
+(grow-dir-lg) creating and checking "/x/file24"
+(grow-dir-lg) creating and checking "/x/file25"
+(grow-dir-lg) creating and checking "/x/file26"
+(grow-dir-lg) creating and checking "/x/file27"
+(grow-dir-lg) creating and checking "/x/file28"
+(grow-dir-lg) creating and checking "/x/file29"
+(grow-dir-lg) creating and checking "/x/file30"
+(grow-dir-lg) creating and checking "/x/file31"
+(grow-dir-lg) creating and checking "/x/file32"
+(grow-dir-lg) creating and checking "/x/file33"
+(grow-dir-lg) creating and checking "/x/file34"
+(grow-dir-lg) creating and checking "/x/file35"
+(grow-dir-lg) creating and checking "/x/file36"
+(grow-dir-lg) creating and checking "/x/file37"
+(grow-dir-lg) creating and checking "/x/file38"
+(grow-dir-lg) creating and checking "/x/file39"
+(grow-dir-lg) creating and checking "/x/file40"
+(grow-dir-lg) creating and checking "/x/file41"
+(grow-dir-lg) creating and checking "/x/file42"
+(grow-dir-lg) creating and checking "/x/file43"
+(grow-dir-lg) creating and checking "/x/file44"
+(grow-dir-lg) creating and checking "/x/file45"
+(grow-dir-lg) creating and checking "/x/file46"
+(grow-dir-lg) creating and checking "/x/file47"
+(grow-dir-lg) creating and checking "/x/file48"
+(grow-dir-lg) creating and checking "/x/file49"
+(grow-dir-lg) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/grow-dir.inc b/src/tests/filesys/extended/grow-dir.inc
new file mode 100644
index 0000000..bee0ba0
--- /dev/null
+++ b/src/tests/filesys/extended/grow-dir.inc
@@ -0,0 +1,41 @@
+/* -*- c -*- */
+
+#include <syscall.h>
+#include <stdio.h>
+#include "tests/filesys/seq-test.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+static char buf[512];
+
+static size_t
+return_block_size (void)
+{
+ return sizeof buf;
+}
+
+void
+test_main (void)
+{
+ size_t i;
+
+#ifdef DIRECTORY
+ CHECK (mkdir (DIRECTORY), "mkdir %s", DIRECTORY);
+#define DIR_PREFIX DIRECTORY "/"
+#else
+#define DIR_PREFIX ""
+#endif
+ for (i = 0; i < FILE_CNT; i++)
+ {
+ char file_name[128];
+ snprintf (file_name, sizeof file_name, "%sfile%zu", DIR_PREFIX, i);
+
+ msg ("creating and checking \"%s\"", file_name);
+
+ quiet = true;
+ seq_test (file_name,
+ buf, sizeof buf, sizeof buf,
+ return_block_size, NULL);
+ quiet = false;
+ }
+}
diff --git a/src/tests/filesys/extended/grow-file-size-persistence.ck b/src/tests/filesys/extended/grow-file-size-persistence.ck
new file mode 100644
index 0000000..150f383
--- /dev/null
+++ b/src/tests/filesys/extended/grow-file-size-persistence.ck
@@ -0,0 +1,7 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+check_archive ({"testfile" => [random_bytes (2134)]});
+pass;
diff --git a/src/tests/filesys/extended/grow-file-size.c b/src/tests/filesys/extended/grow-file-size.c
new file mode 100644
index 0000000..3ce8588
--- /dev/null
+++ b/src/tests/filesys/extended/grow-file-size.c
@@ -0,0 +1,33 @@
+/* Grows a file from 0 bytes to 2,134 bytes, 37 bytes at a time,
+ and checks that the file's size is reported correctly at each
+ step. */
+
+#include <syscall.h>
+#include "tests/filesys/seq-test.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+static char buf[2134];
+
+static size_t
+return_block_size (void)
+{
+ return 37;
+}
+
+static void
+check_file_size (int fd, long ofs)
+{
+ long size = filesize (fd);
+ if (size != ofs)
+ fail ("filesize not updated properly: should be %ld, actually %ld",
+ ofs, size);
+}
+
+void
+test_main (void)
+{
+ seq_test ("testfile",
+ buf, sizeof buf, 0,
+ return_block_size, check_file_size);
+}
diff --git a/src/tests/filesys/extended/grow-file-size.ck b/src/tests/filesys/extended/grow-file-size.ck
new file mode 100644
index 0000000..d81feff
--- /dev/null
+++ b/src/tests/filesys/extended/grow-file-size.ck
@@ -0,0 +1,17 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(grow-file-size) begin
+(grow-file-size) create "testfile"
+(grow-file-size) open "testfile"
+(grow-file-size) writing "testfile"
+(grow-file-size) close "testfile"
+(grow-file-size) open "testfile" for verification
+(grow-file-size) verified contents of "testfile"
+(grow-file-size) close "testfile"
+(grow-file-size) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/grow-root-lg-persistence.ck b/src/tests/filesys/extended/grow-root-lg-persistence.ck
new file mode 100644
index 0000000..1692f46
--- /dev/null
+++ b/src/tests/filesys/extended/grow-root-lg-persistence.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+my ($fs);
+$fs->{"file$_"} = [random_bytes (512)] foreach 0...49;
+check_archive ($fs);
+pass;
diff --git a/src/tests/filesys/extended/grow-root-lg.c b/src/tests/filesys/extended/grow-root-lg.c
new file mode 100644
index 0000000..d8d6c09
--- /dev/null
+++ b/src/tests/filesys/extended/grow-root-lg.c
@@ -0,0 +1,4 @@
+/* Creates 50 files in the root directory. */
+
+#define FILE_CNT 50
+#include "tests/filesys/extended/grow-dir.inc"
diff --git a/src/tests/filesys/extended/grow-root-lg.ck b/src/tests/filesys/extended/grow-root-lg.ck
new file mode 100644
index 0000000..b174bc9
--- /dev/null
+++ b/src/tests/filesys/extended/grow-root-lg.ck
@@ -0,0 +1,60 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(grow-root-lg) begin
+(grow-root-lg) creating and checking "file0"
+(grow-root-lg) creating and checking "file1"
+(grow-root-lg) creating and checking "file2"
+(grow-root-lg) creating and checking "file3"
+(grow-root-lg) creating and checking "file4"
+(grow-root-lg) creating and checking "file5"
+(grow-root-lg) creating and checking "file6"
+(grow-root-lg) creating and checking "file7"
+(grow-root-lg) creating and checking "file8"
+(grow-root-lg) creating and checking "file9"
+(grow-root-lg) creating and checking "file10"
+(grow-root-lg) creating and checking "file11"
+(grow-root-lg) creating and checking "file12"
+(grow-root-lg) creating and checking "file13"
+(grow-root-lg) creating and checking "file14"
+(grow-root-lg) creating and checking "file15"
+(grow-root-lg) creating and checking "file16"
+(grow-root-lg) creating and checking "file17"
+(grow-root-lg) creating and checking "file18"
+(grow-root-lg) creating and checking "file19"
+(grow-root-lg) creating and checking "file20"
+(grow-root-lg) creating and checking "file21"
+(grow-root-lg) creating and checking "file22"
+(grow-root-lg) creating and checking "file23"
+(grow-root-lg) creating and checking "file24"
+(grow-root-lg) creating and checking "file25"
+(grow-root-lg) creating and checking "file26"
+(grow-root-lg) creating and checking "file27"
+(grow-root-lg) creating and checking "file28"
+(grow-root-lg) creating and checking "file29"
+(grow-root-lg) creating and checking "file30"
+(grow-root-lg) creating and checking "file31"
+(grow-root-lg) creating and checking "file32"
+(grow-root-lg) creating and checking "file33"
+(grow-root-lg) creating and checking "file34"
+(grow-root-lg) creating and checking "file35"
+(grow-root-lg) creating and checking "file36"
+(grow-root-lg) creating and checking "file37"
+(grow-root-lg) creating and checking "file38"
+(grow-root-lg) creating and checking "file39"
+(grow-root-lg) creating and checking "file40"
+(grow-root-lg) creating and checking "file41"
+(grow-root-lg) creating and checking "file42"
+(grow-root-lg) creating and checking "file43"
+(grow-root-lg) creating and checking "file44"
+(grow-root-lg) creating and checking "file45"
+(grow-root-lg) creating and checking "file46"
+(grow-root-lg) creating and checking "file47"
+(grow-root-lg) creating and checking "file48"
+(grow-root-lg) creating and checking "file49"
+(grow-root-lg) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/grow-root-sm-persistence.ck b/src/tests/filesys/extended/grow-root-sm-persistence.ck
new file mode 100644
index 0000000..2b0b8ab
--- /dev/null
+++ b/src/tests/filesys/extended/grow-root-sm-persistence.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+my ($fs);
+$fs->{"file$_"} = [random_bytes (512)] foreach 0...19;
+check_archive ($fs);
+pass;
diff --git a/src/tests/filesys/extended/grow-root-sm.c b/src/tests/filesys/extended/grow-root-sm.c
new file mode 100644
index 0000000..ee375d5
--- /dev/null
+++ b/src/tests/filesys/extended/grow-root-sm.c
@@ -0,0 +1,4 @@
+/* Creates 20 files in the root directory. */
+
+#define FILE_CNT 20
+#include "tests/filesys/extended/grow-dir.inc"
diff --git a/src/tests/filesys/extended/grow-root-sm.ck b/src/tests/filesys/extended/grow-root-sm.ck
new file mode 100644
index 0000000..1aac7e9
--- /dev/null
+++ b/src/tests/filesys/extended/grow-root-sm.ck
@@ -0,0 +1,30 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(grow-root-sm) begin
+(grow-root-sm) creating and checking "file0"
+(grow-root-sm) creating and checking "file1"
+(grow-root-sm) creating and checking "file2"
+(grow-root-sm) creating and checking "file3"
+(grow-root-sm) creating and checking "file4"
+(grow-root-sm) creating and checking "file5"
+(grow-root-sm) creating and checking "file6"
+(grow-root-sm) creating and checking "file7"
+(grow-root-sm) creating and checking "file8"
+(grow-root-sm) creating and checking "file9"
+(grow-root-sm) creating and checking "file10"
+(grow-root-sm) creating and checking "file11"
+(grow-root-sm) creating and checking "file12"
+(grow-root-sm) creating and checking "file13"
+(grow-root-sm) creating and checking "file14"
+(grow-root-sm) creating and checking "file15"
+(grow-root-sm) creating and checking "file16"
+(grow-root-sm) creating and checking "file17"
+(grow-root-sm) creating and checking "file18"
+(grow-root-sm) creating and checking "file19"
+(grow-root-sm) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/grow-seq-lg-persistence.ck b/src/tests/filesys/extended/grow-seq-lg-persistence.ck
new file mode 100644
index 0000000..41aaae0
--- /dev/null
+++ b/src/tests/filesys/extended/grow-seq-lg-persistence.ck
@@ -0,0 +1,7 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+check_archive ({"testme" => [random_bytes (72943)]});
+pass;
diff --git a/src/tests/filesys/extended/grow-seq-lg.c b/src/tests/filesys/extended/grow-seq-lg.c
new file mode 100644
index 0000000..3108d17
--- /dev/null
+++ b/src/tests/filesys/extended/grow-seq-lg.c
@@ -0,0 +1,5 @@
+/* Grows a file from 0 bytes to 72,943 bytes, 1,234 bytes at a
+ time. */
+
+#define TEST_SIZE 72943
+#include "tests/filesys/extended/grow-seq.inc"
diff --git a/src/tests/filesys/extended/grow-seq-lg.ck b/src/tests/filesys/extended/grow-seq-lg.ck
new file mode 100644
index 0000000..90fcd8c
--- /dev/null
+++ b/src/tests/filesys/extended/grow-seq-lg.ck
@@ -0,0 +1,17 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(grow-seq-lg) begin
+(grow-seq-lg) create "testme"
+(grow-seq-lg) open "testme"
+(grow-seq-lg) writing "testme"
+(grow-seq-lg) close "testme"
+(grow-seq-lg) open "testme" for verification
+(grow-seq-lg) verified contents of "testme"
+(grow-seq-lg) close "testme"
+(grow-seq-lg) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/grow-seq-sm-persistence.ck b/src/tests/filesys/extended/grow-seq-sm-persistence.ck
new file mode 100644
index 0000000..6cb0bd8
--- /dev/null
+++ b/src/tests/filesys/extended/grow-seq-sm-persistence.ck
@@ -0,0 +1,7 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+check_archive ({"testme" => [random_bytes (5678)]});
+pass;
diff --git a/src/tests/filesys/extended/grow-seq-sm.c b/src/tests/filesys/extended/grow-seq-sm.c
new file mode 100644
index 0000000..3656e2e
--- /dev/null
+++ b/src/tests/filesys/extended/grow-seq-sm.c
@@ -0,0 +1,5 @@
+/* Grows a file from 0 bytes to 5,678 bytes, 1,234 bytes at a
+ time. */
+
+#define TEST_SIZE 5678
+#include "tests/filesys/extended/grow-seq.inc"
diff --git a/src/tests/filesys/extended/grow-seq-sm.ck b/src/tests/filesys/extended/grow-seq-sm.ck
new file mode 100644
index 0000000..5cf4518
--- /dev/null
+++ b/src/tests/filesys/extended/grow-seq-sm.ck
@@ -0,0 +1,17 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(grow-seq-sm) begin
+(grow-seq-sm) create "testme"
+(grow-seq-sm) open "testme"
+(grow-seq-sm) writing "testme"
+(grow-seq-sm) close "testme"
+(grow-seq-sm) open "testme" for verification
+(grow-seq-sm) verified contents of "testme"
+(grow-seq-sm) close "testme"
+(grow-seq-sm) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/grow-seq.inc b/src/tests/filesys/extended/grow-seq.inc
new file mode 100644
index 0000000..1b7710c
--- /dev/null
+++ b/src/tests/filesys/extended/grow-seq.inc
@@ -0,0 +1,20 @@
+/* -*- c -*- */
+
+#include "tests/filesys/seq-test.h"
+#include "tests/main.h"
+
+static char buf[TEST_SIZE];
+
+static size_t
+return_block_size (void)
+{
+ return 1234;
+}
+
+void
+test_main (void)
+{
+ seq_test ("testme",
+ buf, sizeof buf, 0,
+ return_block_size, NULL);
+}
diff --git a/src/tests/filesys/extended/grow-sparse-persistence.ck b/src/tests/filesys/extended/grow-sparse-persistence.ck
new file mode 100644
index 0000000..3f06a5b
--- /dev/null
+++ b/src/tests/filesys/extended/grow-sparse-persistence.ck
@@ -0,0 +1,6 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_archive ({"testfile" => ["\0" x 76543]});
+pass;
diff --git a/src/tests/filesys/extended/grow-sparse.c b/src/tests/filesys/extended/grow-sparse.c
new file mode 100644
index 0000000..6eab210
--- /dev/null
+++ b/src/tests/filesys/extended/grow-sparse.c
@@ -0,0 +1,25 @@
+/* Tests that seeking past the end of a file and writing will
+ properly zero out the region in between. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+static char buf[76543];
+
+void
+test_main (void)
+{
+ const char *file_name = "testfile";
+ char zero = 0;
+ int fd;
+
+ CHECK (create (file_name, 0), "create \"%s\"", file_name);
+ CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name);
+ msg ("seek \"%s\"", file_name);
+ seek (fd, sizeof buf - 1);
+ CHECK (write (fd, &zero, 1) > 0, "write \"%s\"", file_name);
+ msg ("close \"%s\"", file_name);
+ close (fd);
+ check_file (file_name, buf, sizeof buf);
+}
diff --git a/src/tests/filesys/extended/grow-sparse.ck b/src/tests/filesys/extended/grow-sparse.ck
new file mode 100644
index 0000000..379ba2c
--- /dev/null
+++ b/src/tests/filesys/extended/grow-sparse.ck
@@ -0,0 +1,17 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(grow-sparse) begin
+(grow-sparse) create "testfile"
+(grow-sparse) open "testfile"
+(grow-sparse) seek "testfile"
+(grow-sparse) write "testfile"
+(grow-sparse) close "testfile"
+(grow-sparse) open "testfile" for verification
+(grow-sparse) verified contents of "testfile"
+(grow-sparse) close "testfile"
+(grow-sparse) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/grow-tell-persistence.ck b/src/tests/filesys/extended/grow-tell-persistence.ck
new file mode 100644
index 0000000..d93a422
--- /dev/null
+++ b/src/tests/filesys/extended/grow-tell-persistence.ck
@@ -0,0 +1,7 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+check_archive ({"foobar" => [random_bytes (2134)]});
+pass;
diff --git a/src/tests/filesys/extended/grow-tell.c b/src/tests/filesys/extended/grow-tell.c
new file mode 100644
index 0000000..5f5da5b
--- /dev/null
+++ b/src/tests/filesys/extended/grow-tell.c
@@ -0,0 +1,32 @@
+/* Checks that growing a file updates the file position
+ correctly. */
+
+#include <syscall.h>
+#include "tests/filesys/seq-test.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+static char buf[2134];
+
+static size_t
+return_block_size (void)
+{
+ return 37;
+}
+
+static void
+check_tell (int fd, long ofs)
+{
+ long pos = tell (fd);
+ if (pos != ofs)
+ fail ("file position not updated properly: should be %ld, actually %ld",
+ ofs, pos);
+}
+
+void
+test_main (void)
+{
+ seq_test ("foobar",
+ buf, sizeof buf, 0,
+ return_block_size, check_tell);
+}
diff --git a/src/tests/filesys/extended/grow-tell.ck b/src/tests/filesys/extended/grow-tell.ck
new file mode 100644
index 0000000..fe94707
--- /dev/null
+++ b/src/tests/filesys/extended/grow-tell.ck
@@ -0,0 +1,17 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(grow-tell) begin
+(grow-tell) create "foobar"
+(grow-tell) open "foobar"
+(grow-tell) writing "foobar"
+(grow-tell) close "foobar"
+(grow-tell) open "foobar" for verification
+(grow-tell) verified contents of "foobar"
+(grow-tell) close "foobar"
+(grow-tell) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/grow-two-files-persistence.ck b/src/tests/filesys/extended/grow-two-files-persistence.ck
new file mode 100644
index 0000000..1c4ced1
--- /dev/null
+++ b/src/tests/filesys/extended/grow-two-files-persistence.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+my ($a) = random_bytes (8143);
+my ($b) = random_bytes (8143);
+check_archive ({"a" => [$a], "b" => [$b]});
+pass;
diff --git a/src/tests/filesys/extended/grow-two-files.c b/src/tests/filesys/extended/grow-two-files.c
new file mode 100644
index 0000000..6a8fb1c
--- /dev/null
+++ b/src/tests/filesys/extended/grow-two-files.c
@@ -0,0 +1,62 @@
+/* Grows two files in parallel and checks that their contents are
+ correct. */
+
+#include <random.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#define FILE_SIZE 8143
+static char buf_a[FILE_SIZE];
+static char buf_b[FILE_SIZE];
+
+static void
+write_some_bytes (const char *file_name, int fd, const char *buf, size_t *ofs)
+{
+ if (*ofs < FILE_SIZE)
+ {
+ size_t block_size = random_ulong () % (FILE_SIZE / 8) + 1;
+ size_t ret_val;
+ if (block_size > FILE_SIZE - *ofs)
+ block_size = FILE_SIZE - *ofs;
+
+ ret_val = write (fd, buf + *ofs, block_size);
+ if (ret_val != block_size)
+ fail ("write %zu bytes at offset %zu in \"%s\" returned %zu",
+ block_size, *ofs, file_name, ret_val);
+ *ofs += block_size;
+ }
+}
+
+void
+test_main (void)
+{
+ int fd_a, fd_b;
+ size_t ofs_a = 0, ofs_b = 0;
+
+ random_init (0);
+ random_bytes (buf_a, sizeof buf_a);
+ random_bytes (buf_b, sizeof buf_b);
+
+ CHECK (create ("a", 0), "create \"a\"");
+ CHECK (create ("b", 0), "create \"b\"");
+
+ CHECK ((fd_a = open ("a")) > 1, "open \"a\"");
+ CHECK ((fd_b = open ("b")) > 1, "open \"b\"");
+
+ msg ("write \"a\" and \"b\" alternately");
+ while (ofs_a < FILE_SIZE || ofs_b < FILE_SIZE)
+ {
+ write_some_bytes ("a", fd_a, buf_a, &ofs_a);
+ write_some_bytes ("b", fd_b, buf_b, &ofs_b);
+ }
+
+ msg ("close \"a\"");
+ close (fd_a);
+
+ msg ("close \"b\"");
+ close (fd_b);
+
+ check_file ("a", buf_a, FILE_SIZE);
+ check_file ("b", buf_b, FILE_SIZE);
+}
diff --git a/src/tests/filesys/extended/grow-two-files.ck b/src/tests/filesys/extended/grow-two-files.ck
new file mode 100644
index 0000000..b5e754a
--- /dev/null
+++ b/src/tests/filesys/extended/grow-two-files.ck
@@ -0,0 +1,23 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(grow-two-files) begin
+(grow-two-files) create "a"
+(grow-two-files) create "b"
+(grow-two-files) open "a"
+(grow-two-files) open "b"
+(grow-two-files) write "a" and "b" alternately
+(grow-two-files) close "a"
+(grow-two-files) close "b"
+(grow-two-files) open "a" for verification
+(grow-two-files) verified contents of "a"
+(grow-two-files) close "a"
+(grow-two-files) open "b" for verification
+(grow-two-files) verified contents of "b"
+(grow-two-files) close "b"
+(grow-two-files) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/mk-tree.c b/src/tests/filesys/extended/mk-tree.c
new file mode 100644
index 0000000..a36bb88
--- /dev/null
+++ b/src/tests/filesys/extended/mk-tree.c
@@ -0,0 +1,67 @@
+/* Library function for creating a tree of directories. */
+
+#include <stdio.h>
+#include <syscall.h>
+#include "tests/filesys/extended/mk-tree.h"
+#include "tests/lib.h"
+
+static void do_mkdir (const char *format, ...) PRINTF_FORMAT (1, 2);
+static void do_touch (const char *format, ...) PRINTF_FORMAT (1, 2);
+
+void
+make_tree (int at, int bt, int ct, int dt)
+{
+ char try[128];
+ int a, b, c, d;
+ int fd;
+
+ msg ("creating /0/0/0/0 through /%d/%d/%d/%d...",
+ at - 1, bt - 1, ct - 1, dt - 1);
+ quiet = true;
+ for (a = 0; a < at; a++)
+ {
+ do_mkdir ("/%d", a);
+ for (b = 0; b < bt; b++)
+ {
+ do_mkdir ("/%d/%d", a, b);
+ for (c = 0; c < ct; c++)
+ {
+ do_mkdir ("/%d/%d/%d", a, b, c);
+ for (d = 0; d < dt; d++)
+ do_touch ("/%d/%d/%d/%d", a, b, c, d);
+ }
+ }
+ }
+ quiet = false;
+
+ snprintf (try, sizeof try, "/%d/%d/%d/%d", 0, bt - 1, 0, dt - 1);
+ CHECK ((fd = open (try)) > 1, "open \"%s\"", try);
+ msg ("close \"%s\"", try);
+ close (fd);
+}
+
+static void
+do_mkdir (const char *format, ...)
+{
+ char dir[128];
+ va_list args;
+
+ va_start (args, format);
+ vsnprintf (dir, sizeof dir, format, args);
+ va_end (args);
+
+ CHECK (mkdir (dir), "mkdir \"%s\"", dir);
+}
+
+static void
+do_touch (const char *format, ...)
+{
+ char file[128];
+ va_list args;
+
+ va_start (args, format);
+ vsnprintf (file, sizeof file, format, args);
+ va_end (args);
+
+ CHECK (create (file, 0), "create \"%s\"", file);
+}
diff --git a/src/tests/filesys/extended/mk-tree.h b/src/tests/filesys/extended/mk-tree.h
new file mode 100644
index 0000000..df0d5a6
--- /dev/null
+++ b/src/tests/filesys/extended/mk-tree.h
@@ -0,0 +1,6 @@
+#ifndef TESTS_FILESYS_EXTENDED_MK_TREE_H
+#define TESTS_FILESYS_EXTENDED_MK_TREE_H
+
+void make_tree (int at, int bt, int ct, int dt);
+
+#endif /* tests/filesys/extended/mk-tree.h */
diff --git a/src/tests/filesys/extended/syn-rw-persistence.ck b/src/tests/filesys/extended/syn-rw-persistence.ck
new file mode 100644
index 0000000..62d57ee
--- /dev/null
+++ b/src/tests/filesys/extended/syn-rw-persistence.ck
@@ -0,0 +1,8 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+check_archive ({"child-syn-rw" => "tests/filesys/extended/child-syn-rw",
+ "logfile" => [random_bytes (8 * 512)]});
+pass;
diff --git a/src/tests/filesys/extended/syn-rw.c b/src/tests/filesys/extended/syn-rw.c
new file mode 100644
index 0000000..657dfb5
--- /dev/null
+++ b/src/tests/filesys/extended/syn-rw.c
@@ -0,0 +1,35 @@
+/* Grows a file in chunks while subprocesses read the growing
+ file. */
+
+#include <random.h>
+#include <syscall.h>
+#include "tests/filesys/extended/syn-rw.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+char buf[BUF_SIZE];
+
+#define CHILD_CNT 4
+
+void
+test_main (void)
+{
+ pid_t children[CHILD_CNT];
+ size_t ofs;
+ int fd;
+
+ CHECK (create (file_name, 0), "create \"%s\"", file_name);
+ CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name);
+
+ exec_children ("child-syn-rw", children, CHILD_CNT);
+
+ random_bytes (buf, sizeof buf);
+ quiet = true;
+ for (ofs = 0; ofs < BUF_SIZE; ofs += CHUNK_SIZE)
+ CHECK (write (fd, buf + ofs, CHUNK_SIZE) > 0,
+ "write %d bytes at offset %zu in \"%s\"",
+ (int) CHUNK_SIZE, ofs, file_name);
+ quiet = false;
+
+ wait_children (children, CHILD_CNT);
+}
diff --git a/src/tests/filesys/extended/syn-rw.ck b/src/tests/filesys/extended/syn-rw.ck
new file mode 100644
index 0000000..ac82aa8
--- /dev/null
+++ b/src/tests/filesys/extended/syn-rw.ck
@@ -0,0 +1,20 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(syn-rw) begin
+(syn-rw) create "logfile"
+(syn-rw) open "logfile"
+(syn-rw) exec child 1 of 4: "child-syn-rw 0"
+(syn-rw) exec child 2 of 4: "child-syn-rw 1"
+(syn-rw) exec child 3 of 4: "child-syn-rw 2"
+(syn-rw) exec child 4 of 4: "child-syn-rw 3"
+(syn-rw) wait for child 1 of 4 returned 0 (expected 0)
+(syn-rw) wait for child 2 of 4 returned 1 (expected 1)
+(syn-rw) wait for child 3 of 4 returned 2 (expected 2)
+(syn-rw) wait for child 4 of 4 returned 3 (expected 3)
+(syn-rw) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/syn-rw.h b/src/tests/filesys/extended/syn-rw.h
new file mode 100644
index 0000000..170aeb4
--- /dev/null
+++ b/src/tests/filesys/extended/syn-rw.h
@@ -0,0 +1,9 @@
+#ifndef TESTS_FILESYS_EXTENDED_SYN_RW_H
+#define TESTS_FILESYS_EXTENDED_SYN_RW_H
+
+#define CHUNK_SIZE 8
+#define CHUNK_CNT 512
+#define BUF_SIZE (CHUNK_SIZE * CHUNK_CNT)
+static const char file_name[] = "logfile";
+
+#endif /* tests/filesys/extended/syn-rw.h */
diff --git a/src/tests/filesys/extended/tar.c b/src/tests/filesys/extended/tar.c
new file mode 100644
index 0000000..6f70d97
--- /dev/null
+++ b/src/tests/filesys/extended/tar.c
@@ -0,0 +1,250 @@
+/* tar.c
+
+ Creates a tar archive. */
+
+#include <syscall.h>
+#include <stdio.h>
+#include <string.h>
+
+static void usage (void);
+static bool make_tar_archive (const char *archive_name,
+ char *files[], size_t file_cnt);
+
+int
+main (int argc, char *argv[])
+{
+ if (argc < 3)
+ usage ();
+
+ return (make_tar_archive (argv[1], argv + 2, argc - 2)
+ ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+static void
+usage (void)
+{
+ printf ("tar, tar archive creator\n"
+ "Usage: tar ARCHIVE FILE...\n"
+ "where ARCHIVE is the tar archive to create\n"
+ " and FILE... is a list of files or directories to put into it.\n"
+ "(ARCHIVE itself will not be included in the archive, even if it\n"
+ "is in a directory to be archived.)\n");
+ exit (EXIT_FAILURE);
+}
+
+static bool archive_file (char file_name[], size_t file_name_size,
+ int archive_fd, bool *write_error);
+
+static bool archive_ordinary_file (const char *file_name, int file_fd,
+ int archive_fd, bool *write_error);
+static bool archive_directory (char file_name[], size_t file_name_size,
+ int file_fd, int archive_fd, bool *write_error);
+static bool write_header (const char *file_name,
+ char type_flag, int size, unsigned mode,
+ int archive_fd, bool *write_error);
+
+static bool do_write (int fd, const char *buffer, int size, bool *write_error);
+
+static bool
+make_tar_archive (const char *archive_name, char *files[], size_t file_cnt)
+{
+ static const char zeros[512];
+ int archive_fd;
+ bool success = true;
+ bool write_error = false;
+ size_t i;
+
+ if (!create (archive_name, 0))
+ {
+ printf ("%s: create failed\n", archive_name);
+ return false;
+ }
+ archive_fd = open (archive_name);
+ if (archive_fd < 0)
+ {
+ printf ("%s: open failed\n", archive_name);
+ return false;
+ }
+
+ for (i = 0; i < file_cnt; i++)
+ {
+ char file_name[128];
+
+ strlcpy (file_name, files[i], sizeof file_name);
+ if (!archive_file (file_name, sizeof file_name,
+ archive_fd, &write_error))
+ success = false;
+ }
+
+ if (!do_write (archive_fd, zeros, 512, &write_error)
+ || !do_write (archive_fd, zeros, 512, &write_error))
+ success = false;
+
+ close (archive_fd);
+
+ return success;
+}
+
+static bool
+archive_file (char file_name[], size_t file_name_size,
+ int archive_fd, bool *write_error)
+{
+ int file_fd = open (file_name);
+ if (file_fd >= 0)
+ {
+ bool success;
+
+ if (inumber (file_fd) != inumber (archive_fd))
+ {
+ if (!isdir (file_fd))
+ success = archive_ordinary_file (file_name, file_fd,
+ archive_fd, write_error);
+ else
+ success = archive_directory (file_name, file_name_size, file_fd,
+ archive_fd, write_error);
+ }
+ else
+ {
+ /* Nothing to do: don't try to archive the archive file. */
+ success = true;
+ }
+
+ close (file_fd);
+
+ return success;
+ }
+ else
+ {
+ printf ("%s: open failed\n", file_name);
+ return false;
+ }
+}
+
+static bool
+archive_ordinary_file (const char *file_name, int file_fd,
+ int archive_fd, bool *write_error)
+{
+ bool read_error = false;
+ bool success = true;
+ int file_size = filesize (file_fd);
+
+ if (!write_header (file_name, '0', file_size, 0644, archive_fd, write_error))
+ return false;
+
+ while (file_size > 0)
+ {
+ static char buf[512];
+ int chunk_size = file_size > 512 ? 512 : file_size;
+ int read_retval = read (file_fd, buf, chunk_size);
+ int bytes_read = read_retval > 0 ? read_retval : 0;
+
+ if (bytes_read != chunk_size && !read_error)
+ {
+ printf ("%s: read error\n", file_name);
+ read_error = true;
+ success = false;
+ }
+
+ memset (buf + bytes_read, 0, 512 - bytes_read);
+ if (!do_write (archive_fd, buf, 512, write_error))
+ success = false;
+
+ file_size -= chunk_size;
+ }
+
+ return success;
+}
+
+static bool
+archive_directory (char file_name[], size_t file_name_size, int file_fd,
+ int archive_fd, bool *write_error)
+{
+ size_t dir_len;
+ bool success = true;
+
+ dir_len = strlen (file_name);
+ if (dir_len + 1 + READDIR_MAX_LEN + 1 > file_name_size)
+ {
+ printf ("%s: file name too long\n", file_name);
+ return false;
+ }
+
+ if (!write_header (file_name, '5', 0, 0755, archive_fd, write_error))
+ return false;
+
+ file_name[dir_len] = '/';
+ while (readdir (file_fd, &file_name[dir_len + 1]))
+ if (!archive_file (file_name, file_name_size, archive_fd, write_error))
+ success = false;
+ file_name[dir_len] = '\0';
+
+ return success;
+}
+
+static bool
+write_header (const char *file_name,
+ char type_flag, int size, unsigned mode,
+ int archive_fd, bool *write_error)
+{
+ static char header[512];
+ unsigned chksum;
+ size_t i;
+
+ memset (header, 0, sizeof header);
+
+ /* Drop confusing and possibly dangerous prefixes from
+ FILE_NAME. */
+ while (*file_name == '/'
+ || !memcmp (file_name, "./", 2)
+ || !memcmp (file_name, "../", 3))
+ file_name = strchr (file_name, '/') + 1;
+ if (*file_name == '\0')
+ {
+ /* Dropped *everything* from FILE_NAME.
+ Should only be possible for a directory. */
+ ASSERT (type_flag == '5');
+ return true;
+ }
+ else if (strlen (file_name) > 99)
+ {
+ printf ("%s: file name too long\n", file_name);
+ return false;
+ }
+
+ /* Fill in header except for final checksum. */
+ strlcpy (header, file_name, 100); /* name */
+ snprintf (header + 100, 8, "%07o", mode); /* mode */
+ strlcpy (header + 108, "0000000", 8); /* uid */
+ strlcpy (header + 116, "0000000", 8); /* gid */
+ snprintf (header + 124, 12, "%011o", size); /* size */
+ snprintf (header + 136, 12, "%011o", 1136102400); /* mtime (2006-01-01) */
+ memset (header + 148, ' ', 8); /* chksum */
+ header[156] = type_flag; /* typeflag */
+ strlcpy (header + 257, "ustar", 6); /* magic */
+ strlcpy (header + 263, "00", 3); /* version */
+
+ /* Compute and fill in final checksum. */
+ chksum = 0;
+ for (i = 0; i < 512; i++)
+ chksum += (uint8_t) header[i];
+ snprintf (header + 148, 8, "%07o", chksum);
+
+ /* Write header. */
+ return do_write (archive_fd, header, 512, write_error);
+}
+
+static bool
+do_write (int fd, const char *buffer, int size, bool *write_error)
+{
+ if (write (fd, buffer, size) == size)
+ return true;
+ else
+ {
+ if (!*write_error)
+ {
+ printf ("error writing archive\n");
+ *write_error = true;
+ }
+ return false;
+ }
+}
diff --git a/src/tests/filesys/seq-test.c b/src/tests/filesys/seq-test.c
new file mode 100644
index 0000000..8ce222c
--- /dev/null
+++ b/src/tests/filesys/seq-test.c
@@ -0,0 +1,37 @@
+#include "tests/filesys/seq-test.h"
+#include <random.h>
+#include <syscall.h>
+#include "tests/lib.h"
+
+void
+seq_test (const char *file_name, void *buf, size_t size, size_t initial_size,
+ size_t (*block_size_func) (void),
+ void (*check_func) (int fd, long ofs))
+{
+ size_t ofs;
+ int fd;
+
+ random_bytes (buf, size);
+ CHECK (create (file_name, initial_size), "create \"%s\"", file_name);
+ CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name);
+
+ ofs = 0;
+ msg ("writing \"%s\"", file_name);
+ while (ofs < size)
+ {
+ size_t block_size = block_size_func ();
+ if (block_size > size - ofs)
+ block_size = size - ofs;
+
+ if (write (fd, buf + ofs, block_size) != (int) block_size)
+ fail ("write %zu bytes at offset %zu in \"%s\" failed",
+ block_size, ofs, file_name);
+
+ ofs += block_size;
+ if (check_func != NULL)
+ check_func (fd, ofs);
+ }
+ msg ("close \"%s\"", file_name);
+ close (fd);
+ check_file (file_name, buf, size);
+}
diff --git a/src/tests/filesys/seq-test.h b/src/tests/filesys/seq-test.h
new file mode 100644
index 0000000..0697381
--- /dev/null
+++ b/src/tests/filesys/seq-test.h
@@ -0,0 +1,11 @@
+#ifndef TESTS_FILESYS_SEQ_TEST_H
+#define TESTS_FILESYS_SEQ_TEST_H
+
+#include <stddef.h>
+
+void seq_test (const char *file_name,
+ void *buf, size_t size, size_t initial_size,
+ size_t (*block_size_func) (void),
+ void (*check_func) (int fd, long ofs));
+
+#endif /* tests/filesys/seq-test.h */
diff --git a/src/tests/internal/list.c b/src/tests/internal/list.c
new file mode 100644
index 0000000..836c69e
--- /dev/null
+++ b/src/tests/internal/list.c
@@ -0,0 +1,174 @@
+/* Test program for lib/kernel/list.c.
+
+ Attempts to test the list functionality that is not
+ sufficiently tested elsewhere in Pintos.
+
+ This is not a test we will run on your submitted projects.
+ It is here for completeness.
+*/
+
+#undef NDEBUG
+#include <debug.h>
+#include <list.h>
+#include <random.h>
+#include <stdio.h>
+#include "threads/test.h"
+
+/* Maximum number of elements in a linked list that we will
+ test. */
+#define MAX_SIZE 64
+
+/* A linked list element. */
+struct value
+ {
+ struct list_elem elem; /* List element. */
+ int value; /* Item value. */
+ };
+
+static void shuffle (struct value[], size_t);
+static bool value_less (const struct list_elem *, const struct list_elem *,
+ void *);
+static void verify_list_fwd (struct list *, int size);
+static void verify_list_bkwd (struct list *, int size);
+
+/* Test the linked list implementation. */
+void
+test (void)
+{
+ int size;
+
+ printf ("testing various size lists:");
+ for (size = 0; size < MAX_SIZE; size++)
+ {
+ int repeat;
+
+ printf (" %d", size);
+ for (repeat = 0; repeat < 10; repeat++)
+ {
+ static struct value values[MAX_SIZE * 4];
+ struct list list;
+ struct list_elem *e;
+ int i, ofs;
+
+ /* Put values 0...SIZE in random order in VALUES. */
+ for (i = 0; i < size; i++)
+ values[i].value = i;
+ shuffle (values, size);
+
+ /* Assemble list. */
+ list_init (&list);
+ for (i = 0; i < size; i++)
+ list_push_back (&list, &values[i].elem);
+
+ /* Verify correct minimum and maximum elements. */
+ e = list_min (&list, value_less, NULL);
+ ASSERT (size ? list_entry (e, struct value, elem)->value == 0
+ : e == list_begin (&list));
+ e = list_max (&list, value_less, NULL);
+ ASSERT (size ? list_entry (e, struct value, elem)->value == size - 1
+ : e == list_begin (&list));
+
+ /* Sort and verify list. */
+ list_sort (&list, value_less, NULL);
+ verify_list_fwd (&list, size);
+
+ /* Reverse and verify list. */
+ list_reverse (&list);
+ verify_list_bkwd (&list, size);
+
+ /* Shuffle, insert using list_insert_ordered(),
+ and verify ordering. */
+ shuffle (values, size);
+ list_init (&list);
+ for (i = 0; i < size; i++)
+ list_insert_ordered (&list, &values[i].elem,
+ value_less, NULL);
+ verify_list_fwd (&list, size);
+
+ /* Duplicate some items, uniquify, and verify. */
+ ofs = size;
+ for (e = list_begin (&list); e != list_end (&list);
+ e = list_next (e))
+ {
+ struct value *v = list_entry (e, struct value, elem);
+ int copies = random_ulong () % 4;
+ while (copies-- > 0)
+ {
+ values[ofs].value = v->value;
+ list_insert (e, &values[ofs++].elem);
+ }
+ }
+ ASSERT ((size_t) ofs < sizeof values / sizeof *values);
+ list_unique (&list, NULL, value_less, NULL);
+ verify_list_fwd (&list, size);
+ }
+ }
+
+ printf (" done\n");
+ printf ("list: PASS\n");
+}
+
+/* Shuffles the CNT elements in ARRAY into random order. */
+static void
+shuffle (struct value *array, size_t cnt)
+{
+ size_t i;
+
+ for (i = 0; i < cnt; i++)
+ {
+ size_t j = i + random_ulong () % (cnt - i);
+ struct value t = array[j];
+ array[j] = array[i];
+ array[i] = t;
+ }
+}
+
+/* Returns true if value A is less than value B, false
+ otherwise. */
+static bool
+value_less (const struct list_elem *a_, const struct list_elem *b_,
+ void *aux UNUSED)
+{
+ const struct value *a = list_entry (a_, struct value, elem);
+ const struct value *b = list_entry (b_, struct value, elem);
+
+ return a->value < b->value;
+}
+
+/* Verifies that LIST contains the values 0...SIZE when traversed
+ in forward order. */
+static void
+verify_list_fwd (struct list *list, int size)
+{
+ struct list_elem *e;
+ int i;
+
+ for (i = 0, e = list_begin (list);
+ i < size && e != list_end (list);
+ i++, e = list_next (e))
+ {
+ struct value *v = list_entry (e, struct value, elem);
+ ASSERT (i == v->value);
+ }
+ ASSERT (i == size);
+ ASSERT (e == list_end (list));
+}
+
+/* Verifies that LIST contains the values 0...SIZE when traversed
+ in reverse order. */
+static void
+verify_list_bkwd (struct list *list, int size)
+{
+ struct list_elem *e;
+ int i;
+
+ for (i = 0, e = list_rbegin (list);
+ i < size && e != list_rend (list);
+ i++, e = list_prev (e))
+ {
+ struct value *v = list_entry (e, struct value, elem);
+ ASSERT (i == v->value);
+ }
+ ASSERT (i == size);
+ ASSERT (e == list_rend (list));
+}
diff --git a/src/tests/internal/stdio.c b/src/tests/internal/stdio.c
new file mode 100644
index 0000000..fb60cda
--- /dev/null
+++ b/src/tests/internal/stdio.c
@@ -0,0 +1,208 @@
+/* Test program for printf() in lib/stdio.c.
+
+ Attempts to test printf() functionality that is not
+ sufficiently tested elsewhere in Pintos.
+
+ This is not a test we will run on your submitted projects.
+ It is here for completeness.
+*/
+
+#undef NDEBUG
+#include <limits.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "threads/test.h"
+
+/* Number of failures so far. */
+static int failure_cnt;
+
+static void
+checkf (const char *expect, const char *format, ...)
+{
+ char output[128];
+ va_list args;
+
+ printf ("\"%s\" -> \"%s\": ", format, expect);
+
+ va_start (args, format);
+ vsnprintf (output, sizeof output, format, args);
+ va_end (args);
+
+ if (strcmp (expect, output))
+ {
+ printf ("\nFAIL: actual output \"%s\"\n", output);
+ failure_cnt++;
+ }
+ else
+ printf ("okay\n");
+}
+
+/* Test printf() implementation. */
+void
+test (void)
+{
+ printf ("Testing formats:");
+
+ /* Check that commas show up in the right places, for positive
+ numbers. */
+ checkf ("1", "%'d", 1);
+ checkf ("12", "%'d", 12);
+ checkf ("123", "%'d", 123);
+ checkf ("1,234", "%'d", 1234);
+ checkf ("12,345", "%'d", 12345);
+ checkf ("123,456", "%'ld", 123456L);
+ checkf ("1,234,567", "%'ld", 1234567L);
+ checkf ("12,345,678", "%'ld", 12345678L);
+ checkf ("123,456,789", "%'ld", 123456789L);
+ checkf ("1,234,567,890", "%'ld", 1234567890L);
+ checkf ("12,345,678,901", "%'lld", 12345678901LL);
+ checkf ("123,456,789,012", "%'lld", 123456789012LL);
+ checkf ("1,234,567,890,123", "%'lld", 1234567890123LL);
+ checkf ("12,345,678,901,234", "%'lld", 12345678901234LL);
+ checkf ("123,456,789,012,345", "%'lld", 123456789012345LL);
+ checkf ("1,234,567,890,123,456", "%'lld", 1234567890123456LL);
+ checkf ("12,345,678,901,234,567", "%'lld", 12345678901234567LL);
+ checkf ("123,456,789,012,345,678", "%'lld", 123456789012345678LL);
+ checkf ("1,234,567,890,123,456,789", "%'lld", 1234567890123456789LL);
+
+ /* Check that commas show up in the right places, for positive
+ numbers. */
+ checkf ("-1", "%'d", -1);
+ checkf ("-12", "%'d", -12);
+ checkf ("-123", "%'d", -123);
+ checkf ("-1,234", "%'d", -1234);
+ checkf ("-12,345", "%'d", -12345);
+ checkf ("-123,456", "%'ld", -123456L);
+ checkf ("-1,234,567", "%'ld", -1234567L);
+ checkf ("-12,345,678", "%'ld", -12345678L);
+ checkf ("-123,456,789", "%'ld", -123456789L);
+ checkf ("-1,234,567,890", "%'ld", -1234567890L);
+ checkf ("-12,345,678,901", "%'lld", -12345678901LL);
+ checkf ("-123,456,789,012", "%'lld", -123456789012LL);
+ checkf ("-1,234,567,890,123", "%'lld", -1234567890123LL);
+ checkf ("-12,345,678,901,234", "%'lld", -12345678901234LL);
+ checkf ("-123,456,789,012,345", "%'lld", -123456789012345LL);
+ checkf ("-1,234,567,890,123,456", "%'lld", -1234567890123456LL);
+ checkf ("-12,345,678,901,234,567", "%'lld", -12345678901234567LL);
+ checkf ("-123,456,789,012,345,678", "%'lld", -123456789012345678LL);
+ checkf ("-1,234,567,890,123,456,789", "%'lld", -1234567890123456789LL);
+
+ /* Check signed integer conversions. */
+ checkf (" 0", "%5d", 0);
+ checkf ("0 ", "%-5d", 0);
+ checkf (" +0", "%+5d", 0);
+ checkf ("+0 ", "%+-5d", 0);
+ checkf (" 0", "% 5d", 0);
+ checkf ("00000", "%05d", 0);
+ checkf (" ", "%5.0d", 0);
+ checkf (" 00", "%5.2d", 0);
+ checkf ("0", "%d", 0);
+
+ checkf (" 1", "%5d", 1);
+ checkf ("1 ", "%-5d", 1);
+ checkf (" +1", "%+5d", 1);
+ checkf ("+1 ", "%+-5d", 1);
+ checkf (" 1", "% 5d", 1);
+ checkf ("00001", "%05d", 1);
+ checkf (" 1", "%5.0d", 1);
+ checkf (" 01", "%5.2d", 1);
+ checkf ("1", "%d", 1);
+
+ checkf (" -1", "%5d", -1);
+ checkf ("-1 ", "%-5d", -1);
+ checkf (" -1", "%+5d", -1);
+ checkf ("-1 ", "%+-5d", -1);
+ checkf (" -1", "% 5d", -1);
+ checkf ("-0001", "%05d", -1);
+ checkf (" -1", "%5.0d", -1);
+ checkf (" -01", "%5.2d", -1);
+ checkf ("-1", "%d", -1);
+
+ checkf ("12345", "%5d", 12345);
+ checkf ("12345", "%-5d", 12345);
+ checkf ("+12345", "%+5d", 12345);
+ checkf ("+12345", "%+-5d", 12345);
+ checkf (" 12345", "% 5d", 12345);
+ checkf ("12345", "%05d", 12345);
+ checkf ("12345", "%5.0d", 12345);
+ checkf ("12345", "%5.2d", 12345);
+ checkf ("12345", "%d", 12345);
+
+ checkf ("123456", "%5d", 123456);
+ checkf ("123456", "%-5d", 123456);
+ checkf ("+123456", "%+5d", 123456);
+ checkf ("+123456", "%+-5d", 123456);
+ checkf (" 123456", "% 5d", 123456);
+ checkf ("123456", "%05d", 123456);
+ checkf ("123456", "%5.0d", 123456);
+ checkf ("123456", "%5.2d", 123456);
+ checkf ("123456", "%d", 123456);
+
+ /* Check unsigned integer conversions. */
+ checkf (" 0", "%5u", 0);
+ checkf (" 0", "%5o", 0);
+ checkf (" 0", "%5x", 0);
+ checkf (" 0", "%5X", 0);
+ checkf (" 0", "%#5o", 0);
+ checkf (" 0", "%#5x", 0);
+ checkf (" 0", "%#5X", 0);
+ checkf (" 00000000", "%#10.8x", 0);
+
+ checkf (" 1", "%5u", 1);
+ checkf (" 1", "%5o", 1);
+ checkf (" 1", "%5x", 1);
+ checkf (" 1", "%5X", 1);
+ checkf (" 01", "%#5o", 1);
+ checkf (" 0x1", "%#5x", 1);
+ checkf (" 0X1", "%#5X", 1);
+ checkf ("0x00000001", "%#10.8x", 1);
+
+ checkf ("123456", "%5u", 123456);
+ checkf ("361100", "%5o", 123456);
+ checkf ("1e240", "%5x", 123456);
+ checkf ("1E240", "%5X", 123456);
+ checkf ("0361100", "%#5o", 123456);
+ checkf ("0x1e240", "%#5x", 123456);
+ checkf ("0X1E240", "%#5X", 123456);
+ checkf ("0x0001e240", "%#10.8x", 123456);
+
+ /* Character and string conversions. */
+ checkf ("foobar", "%c%c%c%c%c%c", 'f', 'o', 'o', 'b', 'a', 'r');
+ checkf (" left-right ", "%6s%s%-7s", "left", "-", "right");
+ checkf ("trim", "%.4s", "trimoff");
+ checkf ("%%", "%%%%");
+
+ /* From Cristian Cadar's automatic test case generator. */
+ checkf (" abcdefgh", "%9s", "abcdefgh");
+ checkf ("36657730000", "%- o", (unsigned) 036657730000);
+ checkf ("4139757568", "%- u", (unsigned) 4139757568UL);
+ checkf ("f6bfb000", "%- x", (unsigned) 0xf6bfb000);
+ checkf ("36657730000", "%-to", (ptrdiff_t) 036657730000);
+ checkf ("4139757568", "%-tu", (ptrdiff_t) 4139757568UL);
+ checkf ("-155209728", "%-zi", (size_t) -155209728);
+ checkf ("-155209728", "%-zd", (size_t) -155209728);
+ checkf ("036657730000", "%+#o", (unsigned) 036657730000);
+ checkf ("0xf6bfb000", "%+#x", (unsigned) 0xf6bfb000);
+ checkf ("-155209728", "% zi", (size_t) -155209728);
+ checkf ("-155209728", "% zd", (size_t) -155209728);
+ checkf ("4139757568", "% tu", (ptrdiff_t) 4139757568UL);
+ checkf ("036657730000", "% #o", (unsigned) 036657730000);
+ checkf ("0xf6bfb000", "% #x", (unsigned) 0xf6bfb000);
+ checkf ("0xf6bfb000", "%# x", (unsigned) 0xf6bfb000);
+ checkf ("-155209728", "%#zd", (size_t) -155209728);
+ checkf ("-155209728", "%0zi", (size_t) -155209728);
+ checkf ("4,139,757,568", "%'tu", (ptrdiff_t) 4139757568UL);
+ checkf ("-155,209,728", "%-'d", -155209728);
+ checkf ("-155209728", "%.zi", (size_t) -155209728);
+ checkf ("-155209728", "%zi", (size_t) -155209728);
+ checkf ("-155209728", "%zd", (size_t) -155209728);
+ checkf ("-155209728", "%+zi", (size_t) -155209728);
+
+ if (failure_cnt == 0)
+ printf ("\nstdio: PASS\n");
+ else
+ printf ("\nstdio: FAIL: %d tests failed\n", failure_cnt);
+}
diff --git a/src/tests/internal/stdlib.c b/src/tests/internal/stdlib.c
new file mode 100644
index 0000000..ad0f0f9
--- /dev/null
+++ b/src/tests/internal/stdlib.c
@@ -0,0 +1,114 @@
+/* Test program for sorting and searching in lib/stdlib.c.
+
+ Attempts to test the sorting and searching functionality that
+ is not sufficiently tested elsewhere in Pintos.
+
+ This is not a test we will run on your submitted projects.
+ It is here for completeness.
+*/
+
+#undef NDEBUG
+#include <debug.h>
+#include <limits.h>
+#include <random.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "threads/test.h"
+
+/* Maximum number of elements in an array that we will test. */
+#define MAX_CNT 4096
+
+static void shuffle (int[], size_t);
+static int compare_ints (const void *, const void *);
+static void verify_order (const int[], size_t);
+static void verify_bsearch (const int[], size_t);
+
+/* Test sorting and searching implementations. */
+void
+test (void)
+{
+ int cnt;
+
+ printf ("testing various size arrays:");
+ for (cnt = 0; cnt < MAX_CNT; cnt = cnt * 4 / 3 + 1)
+ {
+ int repeat;
+
+ printf (" %zu", cnt);
+ for (repeat = 0; repeat < 10; repeat++)
+ {
+ static int values[MAX_CNT];
+ int i;
+
+ /* Put values 0...CNT in random order in VALUES. */
+ for (i = 0; i < cnt; i++)
+ values[i] = i;
+ shuffle (values, cnt);
+
+ /* Sort VALUES, then verify ordering. */
+ qsort (values, cnt, sizeof *values, compare_ints);
+ verify_order (values, cnt);
+ verify_bsearch (values, cnt);
+ }
+ }
+
+ printf (" done\n");
+ printf ("stdlib: PASS\n");
+}
+
+/* Shuffles the CNT elements in ARRAY into random order. */
+static void
+shuffle (int *array, size_t cnt)
+{
+ size_t i;
+
+ for (i = 0; i < cnt; i++)
+ {
+ size_t j = i + random_ulong () % (cnt - i);
+ int t = array[j];
+ array[j] = array[i];
+ array[i] = t;
+ }
+}
+
+/* Returns 1 if *A is greater than *B,
+ 0 if *A equals *B,
+ -1 if *A is less than *B. */
+static int
+compare_ints (const void *a_, const void *b_)
+{
+ const int *a = a_;
+ const int *b = b_;
+
+ return *a < *b ? -1 : *a > *b;
+}
+
+/* Verifies that ARRAY contains the CNT ints 0...CNT-1. */
+static void
+verify_order (const int *array, size_t cnt)
+{
+ int i;
+
+ for (i = 0; (size_t) i < cnt; i++)
+ ASSERT (array[i] == i);
+}
+
+/* Checks that bsearch() works properly in ARRAY. ARRAY must
+ contain the values 0...CNT-1. */
+static void
+verify_bsearch (const int *array, size_t cnt)
+{
+ int not_in_array[] = {0, -1, INT_MAX, MAX_CNT, MAX_CNT + 1, MAX_CNT * 2};
+ int i;
+
+ /* Check that all the values in the array are found properly. */
+ for (i = 0; (size_t) i < cnt; i++)
+ ASSERT (bsearch (&i, array, cnt, sizeof *array, compare_ints)
+ == array + i);
+
+ /* Check that some values not in the array are not found. */
+ not_in_array[0] = cnt;
+ for (i = 0; (size_t) i < sizeof not_in_array / sizeof *not_in_array; i++)
+ ASSERT (bsearch (&not_in_array[i], array, cnt, sizeof *array, compare_ints)
+ == NULL);
+}
diff --git a/src/tests/klaar/Make.tests b/src/tests/klaar/Make.tests
new file mode 100644
index 0000000..66b097a
--- /dev/null
+++ b/src/tests/klaar/Make.tests
@@ -0,0 +1,24 @@
+# -*- makefile -*-
+
+tests/%.output: FSDISK = 2
+tests/%.output: PUTFILES = $(filter-out os.dsk, $^)
+
+tests/klaar_TESTS = $(addprefix tests/klaar/,read-bad-buf low-mem \
+exec-corrupt)
+
+tests/klaar_PROGS = $(tests/klaar_TESTS) $(addprefix \
+tests/klaar/,child-simple)
+
+tests/klaar/read-bad-buf_SRC = tests/klaar/read-bad-buf.c tests/main.c
+tests/klaar/low-mem_SRC = tests/klaar/low-mem.c tests/main.c
+tests/klaar/exec-corrupt_SRC += tests/klaar/exec-corrupt.c tests/main.c
+
+tests/klaar/child-simple_SRC = tests/klaar/child-simple.c
+
+$(foreach prog,$(tests/klaar_PROGS),$(eval $(prog)_SRC += tests/lib.c))
+
+tests/klaar/read-bad-buf_PUTFILES += tests/klaar/sample.txt
+tests/klaar/low-mem_PUTFILES += tests/klaar/child-simple
+tests/klaar/exec-corrupt_PUTFILES += tests/klaar/corrupt-elf
+
+tests/klaar/low-mem.output: KERNELFLAGS = -tcl=3
diff --git a/src/tests/klaar/child-simple.c b/src/tests/klaar/child-simple.c
new file mode 100644
index 0000000..0d2dacf
--- /dev/null
+++ b/src/tests/klaar/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/klaar/corrupt-elf b/src/tests/klaar/corrupt-elf
new file mode 100644
index 0000000..456421b
--- /dev/null
+++ b/src/tests/klaar/corrupt-elf
Binary files differ
diff --git a/src/tests/klaar/exec-corrupt.c b/src/tests/klaar/exec-corrupt.c
new file mode 100644
index 0000000..9f83692
--- /dev/null
+++ b/src/tests/klaar/exec-corrupt.c
@@ -0,0 +1,14 @@
+/*
+ Try to load a corrupt executable.
+ (klaar@ida)
+*/
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ msg ("exec(\"corrupt-elf\"): %d", exec ("corrupt-elf"));
+}
diff --git a/src/tests/klaar/exec-corrupt.ck b/src/tests/klaar/exec-corrupt.ck
new file mode 100644
index 0000000..d66eeee
--- /dev/null
+++ b/src/tests/klaar/exec-corrupt.ck
@@ -0,0 +1,31 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF', <<'EOF', <<'EOF']);
+(exec-corrupt) begin
+load: corrupt-elf: error loading executable
+(exec-corrupt) exec("corrupt-elf"): -1
+(exec-corrupt) end
+exec-corrupt: exit(0)
+EOF
+(exec-corrupt) begin
+(exec-corrupt) exec("corrupt-elf"): -1
+(exec-corrupt) end
+exec-corrupt: exit(0)
+EOF
+(exec-corrupt) begin
+load: corrupt-elf: error loading executable
+corrupt-elf: exit(-1)
+(exec-corrupt) exec("corrupt-elf"): -1
+(exec-corrupt) end
+exec-corrupt: exit(0)
+EOF
+(exec-corrupt) begin
+load: corrupt-elf: error loading executable
+(exec-corrupt) exec("corrupt-elf"): -1
+corrupt-elf: exit(-1)
+(exec-corrupt) end
+exec-corrupt: exit(0)
+EOF
+pass;
diff --git a/src/tests/klaar/low-mem.c b/src/tests/klaar/low-mem.c
new file mode 100644
index 0000000..8f1da3e
--- /dev/null
+++ b/src/tests/klaar/low-mem.c
@@ -0,0 +1,15 @@
+/*
+ Simulate a failure in thread_create.
+ A real reason for failure could be low memory.
+ (klaar@ida)
+*/
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ msg ("exec(\"child-simple\"): %d", exec ("child-simple"));
+}
diff --git a/src/tests/klaar/low-mem.ck b/src/tests/klaar/low-mem.ck
new file mode 100644
index 0000000..f128c30
--- /dev/null
+++ b/src/tests/klaar/low-mem.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(low-mem) begin
+(low-mem) exec("child-simple"): -1
+(low-mem) end
+low-mem: exit(0)
+EOF
+pass;
diff --git a/src/tests/klaar/read-bad-buf.c b/src/tests/klaar/read-bad-buf.c
new file mode 100644
index 0000000..1e41562
--- /dev/null
+++ b/src/tests/klaar/read-bad-buf.c
@@ -0,0 +1,24 @@
+/* Passes an semingly valid pointer to the read system call.
+ The pointer will however span invalid pages.
+ The process must be terminated with -1 exit code.
+ (klaar@ida)
+*/
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+char global; /* allocated with process image */
+
+void
+test_main (void)
+{
+ int handle;
+ char local; /* allocated on process stack */
+
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+
+ /* buffer will start and end at valid addresses ... */
+ read (handle, (char *)&global, &local - &global + 1);
+ fail ("should not have survived read()");
+}
diff --git a/src/tests/klaar/read-bad-buf.ck b/src/tests/klaar/read-bad-buf.ck
new file mode 100644
index 0000000..22adc50
--- /dev/null
+++ b/src/tests/klaar/read-bad-buf.ck
@@ -0,0 +1,15 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(read-bad-buf) begin
+(read-bad-buf) open "sample.txt"
+(read-bad-buf) end
+read-bad-buf: exit(0)
+EOF
+(read-bad-buf) begin
+(read-bad-buf) open "sample.txt"
+read-bad-buf: exit(-1)
+EOF
+pass;
diff --git a/src/tests/klaar/sample.txt b/src/tests/klaar/sample.txt
new file mode 100644
index 0000000..5050fec
--- /dev/null
+++ b/src/tests/klaar/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/lib.c b/src/tests/lib.c
new file mode 100644
index 0000000..ee36505
--- /dev/null
+++ b/src/tests/lib.c
@@ -0,0 +1,196 @@
+#include "tests/lib.h"
+#include <random.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <syscall.h>
+
+const char *test_name;
+bool quiet = false;
+
+static void
+vmsg (const char *format, va_list args, const char *suffix)
+{
+ /* We go to some trouble to stuff the entire message into a
+ single buffer and output it in a single system call, because
+ that'll (typically) ensure that it gets sent to the console
+ atomically. Otherwise kernel messages like "foo: exit(0)"
+ can end up being interleaved if we're unlucky. */
+ static char buf[1024];
+
+ snprintf (buf, sizeof buf, "(%s) ", test_name);
+ vsnprintf (buf + strlen (buf), sizeof buf - strlen (buf), format, args);
+ strlcpy (buf + strlen (buf), suffix, sizeof buf - strlen (buf));
+ write (STDOUT_FILENO, buf, strlen (buf));
+}
+
+void
+msg (const char *format, ...)
+{
+ va_list args;
+
+ if (quiet)
+ return;
+ va_start (args, format);
+ vmsg (format, args, "\n");
+ va_end (args);
+}
+
+void
+fail (const char *format, ...)
+{
+ va_list args;
+
+ va_start (args, format);
+ vmsg (format, args, ": FAILED\n");
+ va_end (args);
+
+ exit (1);
+}
+
+static void
+swap (void *a_, void *b_, size_t size)
+{
+ uint8_t *a = a_;
+ uint8_t *b = b_;
+ size_t i;
+
+ for (i = 0; i < size; i++)
+ {
+ uint8_t t = a[i];
+ a[i] = b[i];
+ b[i] = t;
+ }
+}
+
+void
+shuffle (void *buf_, size_t cnt, size_t size)
+{
+ char *buf = buf_;
+ size_t i;
+
+ for (i = 0; i < cnt; i++)
+ {
+ size_t j = i + random_ulong () % (cnt - i);
+ swap (buf + i * size, buf + j * size, size);
+ }
+}
+
+void
+exec_children (const char *child_name, pid_t pids[], size_t child_cnt)
+{
+ size_t i;
+
+ for (i = 0; i < child_cnt; i++)
+ {
+ char cmd_line[128];
+ snprintf (cmd_line, sizeof cmd_line, "%s %zu", child_name, i);
+ CHECK ((pids[i] = exec (cmd_line)) != PID_ERROR,
+ "exec child %zu of %zu: \"%s\"", i + 1, child_cnt, cmd_line);
+ }
+}
+
+void
+wait_children (pid_t pids[], size_t child_cnt)
+{
+ size_t i;
+
+ for (i = 0; i < child_cnt; i++)
+ {
+ int status = wait (pids[i]);
+ CHECK (status == (int) i,
+ "wait for child %zu of %zu returned %d (expected %zu)",
+ i + 1, child_cnt, status, i);
+ }
+}
+
+void
+check_file_handle (int fd,
+ const char *file_name, const void *buf_, size_t size)
+{
+ const char *buf = buf_;
+ size_t ofs = 0;
+ size_t file_size;
+
+ /* Warn about file of wrong size. Don't fail yet because we
+ may still be able to get more information by reading the
+ file. */
+ file_size = filesize (fd);
+ if (file_size != size)
+ msg ("size of %s (%zu) differs from expected (%zu)",
+ file_name, file_size, size);
+
+ /* Read the file block-by-block, comparing data as we go. */
+ while (ofs < size)
+ {
+ char block[512];
+ size_t block_size, ret_val;
+
+ block_size = size - ofs;
+ if (block_size > sizeof block)
+ block_size = sizeof block;
+
+ ret_val = read (fd, block, block_size);
+ if (ret_val != block_size)
+ fail ("read of %zu bytes at offset %zu in \"%s\" returned %zu",
+ block_size, ofs, file_name, ret_val);
+
+ compare_bytes (block, buf + ofs, block_size, ofs, file_name);
+ ofs += block_size;
+ }
+
+ /* Now fail due to wrong file size. */
+ if (file_size != size)
+ fail ("size of %s (%zu) differs from expected (%zu)",
+ file_name, file_size, size);
+
+ msg ("verified contents of \"%s\"", file_name);
+}
+
+void
+check_file (const char *file_name, const void *buf, size_t size)
+{
+ int fd;
+
+ CHECK ((fd = open (file_name)) > 1, "open \"%s\" for verification",
+ file_name);
+ check_file_handle (fd, file_name, buf, size);
+ msg ("close \"%s\"", file_name);
+ close (fd);
+}
+
+void
+compare_bytes (const void *read_data_, const void *expected_data_, size_t size,
+ size_t ofs, const char *file_name)
+{
+ const uint8_t *read_data = read_data_;
+ const uint8_t *expected_data = expected_data_;
+ size_t i, j;
+ size_t show_cnt;
+
+ if (!memcmp (read_data, expected_data, size))
+ return;
+
+ for (i = 0; i < size; i++)
+ if (read_data[i] != expected_data[i])
+ break;
+ for (j = i + 1; j < size; j++)
+ if (read_data[j] == expected_data[j])
+ break;
+
+ quiet = false;
+ msg ("%zu bytes read starting at offset %zu in \"%s\" differ "
+ "from expected.", j - i, ofs + i, file_name);
+ show_cnt = j - i;
+ if (j - i > 64)
+ {
+ show_cnt = 64;
+ msg ("Showing first differing %zu bytes.", show_cnt);
+ }
+ msg ("Data actually read:");
+ hex_dump (ofs + i, read_data + i, show_cnt, true);
+ msg ("Expected data:");
+ hex_dump (ofs + i, expected_data + i, show_cnt, true);
+ fail ("%zu bytes read starting at offset %zu in \"%s\" differ "
+ "from expected", j - i, ofs + i, file_name);
+}
diff --git a/src/tests/lib.h b/src/tests/lib.h
new file mode 100644
index 0000000..648327b
--- /dev/null
+++ b/src/tests/lib.h
@@ -0,0 +1,50 @@
+#ifndef TESTS_LIB_H
+#define TESTS_LIB_H
+
+#include <debug.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <syscall.h>
+
+extern const char *test_name;
+extern bool quiet;
+
+void msg (const char *, ...) PRINTF_FORMAT (1, 2);
+void fail (const char *, ...) PRINTF_FORMAT (1, 2) NO_RETURN;
+
+/* Takes an expression to test for SUCCESS and a message, which
+ may include printf-style arguments. Logs the message, then
+ tests the expression. If it is zero, indicating failure,
+ emits the message as a failure.
+
+ Somewhat tricky to use:
+
+ - SUCCESS must not have side effects that affect the
+ message, because that will cause the original message and
+ the failure message to differ.
+
+ - The message must not have side effects of its own, because
+ it will be printed twice on failure, or zero times on
+ success if quiet is set. */
+#define CHECK(SUCCESS, ...) \
+ do \
+ { \
+ msg (__VA_ARGS__); \
+ if (!(SUCCESS)) \
+ fail (__VA_ARGS__); \
+ } \
+ while (0)
+
+void shuffle (void *, size_t cnt, size_t size);
+
+void exec_children (const char *child_name, pid_t pids[], size_t child_cnt);
+void wait_children (pid_t pids[], size_t child_cnt);
+
+void check_file_handle (int fd, const char *file_name,
+ const void *buf_, size_t filesize);
+void check_file (const char *file_name, const void *buf, size_t filesize);
+
+void compare_bytes (const void *read_data, const void *expected_data,
+ size_t size, size_t ofs, const char *file_name);
+
+#endif /* test/lib.h */
diff --git a/src/tests/lib.pm b/src/tests/lib.pm
new file mode 100644
index 0000000..bc37ae5
--- /dev/null
+++ b/src/tests/lib.pm
@@ -0,0 +1,19 @@
+use strict;
+use warnings;
+
+use tests::random;
+
+sub shuffle {
+ my ($in, $cnt, $sz) = @_;
+ $cnt * $sz == length $in or die;
+ my (@a) = 0...$cnt - 1;
+ for my $i (0...$cnt - 1) {
+ my ($j) = $i + random_ulong () % ($cnt - $i);
+ @a[$i, $j] = @a[$j, $i];
+ }
+ my ($out) = "";
+ $out .= substr ($in, $_ * $sz, $sz) foreach @a;
+ return $out;
+}
+
+1;
diff --git a/src/tests/main.c b/src/tests/main.c
new file mode 100644
index 0000000..ad1b0f1
--- /dev/null
+++ b/src/tests/main.c
@@ -0,0 +1,15 @@
+#include <random.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+int
+main (int argc UNUSED, char *argv[])
+{
+ test_name = argv[0];
+
+ msg ("begin");
+ random_init (0);
+ test_main ();
+ msg ("end");
+ return 0;
+}
diff --git a/src/tests/main.h b/src/tests/main.h
new file mode 100644
index 0000000..f0e8818
--- /dev/null
+++ b/src/tests/main.h
@@ -0,0 +1,6 @@
+#ifndef TESTS_MAIN_H
+#define TESTS_MAIN_H
+
+void test_main (void);
+
+#endif /* tests/main.h */
diff --git a/src/tests/make-grade b/src/tests/make-grade
new file mode 100644
index 0000000..a3faa0e
--- /dev/null
+++ b/src/tests/make-grade
@@ -0,0 +1,152 @@
+#! /usr/bin/perl
+
+use strict;
+use warnings;
+
+@ARGV == 3 || die;
+my ($src_dir, $results_file, $grading_file) = @ARGV;
+
+# Read pass/file verdicts from $results_file.
+open (RESULTS, '<', $results_file) || die "$results_file: open: $!\n";
+my (%verdicts, %verdict_counts);
+while (<RESULTS>) {
+ my ($verdict, $test) = /^(pass|FAIL) (.*)$/ or die;
+ $verdicts{$test} = $verdict eq 'pass';
+}
+close RESULTS;
+
+my (@failures);
+my (@overall, @rubrics, @summary);
+my ($pct_actual, $pct_possible) = (0, 0);
+
+# Read grading file.
+my (@items);
+open (GRADING, '<', $grading_file) || die "$grading_file: open: $!\n";
+while (<GRADING>) {
+ s/#.*//;
+ next if /^\s*$/;
+ my ($max_pct, $rubric_suffix) = /^\s*(\d+(?:\.\d+)?)%\t(.*)/ or die;
+ my ($dir) = $rubric_suffix =~ /^(.*)\//;
+ my ($rubric_file) = "$src_dir/$rubric_suffix";
+ open (RUBRIC, '<', $rubric_file) or die "$rubric_file: open: $!\n";
+
+ # Rubric file must begin with title line.
+ my $title = <RUBRIC>;
+ chomp $title;
+ $title =~ s/:$// or die;
+ $title .= " ($rubric_suffix):";
+ push (@rubrics, $title);
+
+ my ($score, $possible) = (0, 0);
+ my ($cnt, $passed) = (0, 0);
+ my ($was_score) = 0;
+ while (<RUBRIC>) {
+ chomp;
+ push (@rubrics, "\t$_"), next if /^-/;
+ push (@rubrics, ""), next if /^\s*$/;
+ my ($poss, $name) = /^(\d+)\t(.*)$/ or die;
+ my ($test) = "$dir/$name";
+ my ($points) = 0;
+ if (!defined $verdicts{$test}) {
+ push (@overall, "warning: $test not tested, assuming failure");
+ } elsif ($verdicts{$test}) {
+ $points = $poss;
+ $passed++;
+ }
+ push (@failures, $test) if !$points;
+ $verdict_counts{$test}++;
+ push (@rubrics, sprintf ("\t%4s%2d/%2d %s",
+ $points ? '' : '**', $points, $poss, $test));
+ $score += $points;
+ $possible += $poss;
+ $cnt++;
+ }
+ close (RUBRIC);
+
+ push (@rubrics, "");
+ push (@rubrics, "\t- Section summary.");
+ push (@rubrics, sprintf ("\t%4s%3d/%3d %s",
+ '', $passed, $cnt, 'tests passed'));
+ push (@rubrics, sprintf ("\t%4s%3d/%3d %s",
+ '', $score, $possible, 'points subtotal'));
+ push (@rubrics, '');
+
+ my ($pct) = ($score / $possible) * $max_pct;
+ push (@summary, sprintf ("%-45s %3d/%3d %5.1f%%/%5.1f%%",
+ $rubric_suffix,
+ $score, $possible,
+ $pct, $max_pct));
+ $pct_actual += $pct;
+ $pct_possible += $max_pct;
+}
+close GRADING;
+
+my ($sum_line)
+ = "--------------------------------------------- --- --- ------ ------";
+unshift (@summary,
+ "SUMMARY BY TEST SET",
+ '',
+ sprintf ("%-45s %3s %3s %6s %6s",
+ "Test Set", "Pts", "Max", "% Ttl", "% Max"),
+ $sum_line);
+push (@summary,
+ $sum_line,
+ sprintf ("%-45s %3s %3s %5.1f%%/%5.1f%%",
+ 'Total', '', '', $pct_actual, $pct_possible));
+
+unshift (@rubrics,
+ "SUMMARY OF INDIVIDUAL TESTS",
+ '');
+
+foreach my $name (keys (%verdicts)) {
+ my ($count) = $verdict_counts{$name};
+ if (!defined ($count) || $count != 1) {
+ if (!defined ($count) || !$count) {
+ push (@overall, "warning: test $name doesn't count for grading");
+ } else {
+ push (@overall,
+ "warning: test $name counted $count times in grading");
+ }
+ }
+}
+push (@overall, sprintf ("TOTAL TESTING SCORE: %.1f%%", $pct_actual));
+if (sprintf ("%.1f", $pct_actual) eq sprintf ("%.1f", $pct_possible)) {
+ push (@overall, "ALL TESTED PASSED -- PERFECT SCORE");
+}
+
+my (@divider) = ('', '- ' x 38, '');
+
+print map ("$_\n", @overall, @divider, @summary, @divider, @rubrics);
+
+for my $test (@failures) {
+ print map ("$_\n", @divider);
+ print "DETAILS OF $test FAILURE:\n\n";
+
+ if (open (RESULT, '<', "$test.result")) {
+ my $first_line = <RESULT>;
+ my ($cnt) = 0;
+ while (<RESULT>) {
+ print;
+ $cnt++;
+ }
+ close (RESULT);
+ }
+
+ if (open (OUTPUT, '<', "$test.output")) {
+ print "\nOUTPUT FROM $test:\n\n";
+
+ my ($panics, $boots) = (0, 0);
+ while (<OUTPUT>) {
+ if (/PANIC/ && ++$panics > 2) {
+ print "[...details of additional panic(s) omitted...]\n";
+ last;
+ }
+ print;
+ if (/Pintos booting/ && ++$boots > 1) {
+ print "[...details of reboot(s) omitted...]\n";
+ last;
+ }
+ }
+ close (OUTPUT);
+ }
+}
diff --git a/src/tests/random.pm b/src/tests/random.pm
new file mode 100644
index 0000000..be008ff
--- /dev/null
+++ b/src/tests/random.pm
@@ -0,0 +1,27 @@
+use strict;
+use warnings;
+
+use tests::arc4;
+
+my (@arc4);
+
+sub random_init {
+ if (@arc4 == 0) {
+ my ($seed) = @_;
+ $seed = 0 if !defined $seed;
+ @arc4 = arc4_init (pack ("V", $seed));
+ }
+}
+
+sub random_bytes {
+ random_init ();
+ my ($n) = @_;
+ return arc4_crypt (\@arc4, "\0" x $n);
+}
+
+sub random_ulong {
+ random_init ();
+ return unpack ("V", random_bytes (4));
+}
+
+1;
diff --git a/src/tests/tests.pm b/src/tests/tests.pm
new file mode 100644
index 0000000..29e0707
--- /dev/null
+++ b/src/tests/tests.pm
@@ -0,0 +1,622 @@
+use strict;
+use warnings;
+use tests::Algorithm::Diff;
+use File::Temp 'tempfile';
+use Fcntl qw(SEEK_SET SEEK_CUR);
+
+sub fail;
+sub pass;
+
+die if @ARGV != 2;
+our ($test, $src_dir) = @ARGV;
+
+my ($msg_file) = tempfile ();
+select ($msg_file);
+
+our (@prereq_tests) = ();
+if ($test =~ /^(.*)-persistence$/) {
+ push (@prereq_tests, $1);
+}
+for my $prereq_test (@prereq_tests) {
+ my (@result) = read_text_file ("$prereq_test.result");
+ fail "Prerequisite test $prereq_test failed.\n" if $result[0] ne 'PASS';
+}
+
+
+# Generic testing.
+
+sub check_expected {
+ my ($expected) = pop @_;
+ my (@options) = @_;
+ my (@output) = read_text_file ("$test.output");
+ common_checks ("run", @output);
+ compare_output ("run", @options, \@output, $expected);
+}
+
+sub common_checks {
+ my ($run, @output) = @_;
+
+ fail "\u$run produced no output at all\n" if @output == 0;
+
+ check_for_panic ($run, @output);
+ check_for_keyword ($run, "FAIL", @output);
+ check_for_triple_fault ($run, @output);
+ check_for_keyword ($run, "TIMEOUT", @output);
+
+ fail "\u$run didn't start up properly: no \"Pintos booting\" message\n"
+ if !grep (/Pintos booting with.*kB RAM\.\.\./, @output);
+ fail "\u$run didn't start up properly: no \"Boot complete\" message\n"
+ if !grep (/Boot complete/, @output);
+ fail "\u$run didn't shut down properly: no \"Timer: # ticks\" message\n"
+ if !grep (/Timer: \d+ ticks/, @output);
+ fail "\u$run didn't shut down properly: no \"Powering off\" message\n"
+ if !grep (/Powering off/, @output);
+}
+
+sub check_for_panic {
+ my ($run, @output) = @_;
+
+ my ($panic) = grep (/PANIC/, @output);
+ return unless defined $panic;
+
+ print "Kernel panic in $run: ", substr ($panic, index ($panic, "PANIC")),
+ "\n";
+
+ my (@stack_line) = grep (/Call stack:/, @output);
+ if (@stack_line != 0) {
+ my ($addrs) = $stack_line[0] =~ /Call stack:((?: 0x[0-9a-f]+)+)/;
+
+ # Find a user program to translate user virtual addresses.
+ my ($userprog) = "";
+ $userprog = "$test"
+ if grep (hex ($_) < 0xc0000000, split (' ', $addrs)) > 0 && -e $test;
+
+ # Get and print the backtrace.
+ my ($trace) = scalar (`backtrace kernel.o $userprog $addrs`);
+ print "Call stack:$addrs\n";
+ print "Translation of call stack:\n";
+ print $trace;
+
+ # Print disclaimer.
+ if ($userprog ne '' && index ($trace, $userprog) >= 0) {
+ print <<EOF;
+Translations of user virtual addresses above are based on a guess at
+the binary to use. If this guess is incorrect, then those
+translations will be misleading.
+EOF
+ }
+ }
+
+ if ($panic =~ /sec_no \< d-\>capacity/) {
+ print <<EOF;
+\nThis assertion commonly fails when accessing a file via an inode that
+has been closed and freed. Freeing an inode clears all its sector
+indexes to 0xcccccccc, which is not a valid sector number for disks
+smaller than about 1.6 TB.
+EOF
+ }
+
+ fail;
+}
+
+sub check_for_keyword {
+ my ($run, $keyword, @output) = @_;
+
+ my ($kw_line) = grep (/$keyword/, @output);
+ return unless defined $kw_line;
+
+ # Most output lines are prefixed by (test-name). Eliminate this
+ # from our message for brevity.
+ $kw_line =~ s/^\([^\)]+\)\s+//;
+ print "$run: $kw_line\n";
+
+ fail;
+}
+
+sub check_for_triple_fault {
+ my ($run, @output) = @_;
+
+ my ($reboots) = grep (/Pintos booting/, @output) - 1;
+ return unless $reboots > 0;
+
+ print <<EOF;
+\u$run spontaneously rebooted $reboots times.
+This is most often caused by unhandled page faults.
+Read the Triple Faults section in the Debugging chapter
+of the Pintos manual for more information.
+EOF
+
+ fail;
+}
+
+# Get @output without header or trailer.
+sub get_core_output {
+ my ($run, @output) = @_;
+ my ($p);
+
+ my ($process);
+ my ($start);
+ for my $i (0...$#_) {
+ $start = $i + 1, last
+ if ($process) = $output[$i] =~ /^Executing '(\S+).*':$/;
+ }
+
+ my ($end);
+ for my $i ($start...$#output) {
+ $end = $i - 1, last if $output[$i] =~ /^Execution of '.*' complete.$/;
+ }
+
+ fail "\u$run didn't start a thread or process\n" if !defined $start;
+ fail "\u$run started '$process' but it never finished\n" if !defined $end;
+
+ return @output[$start...$end];
+}
+
+sub compare_output {
+ my ($run) = shift @_;
+ my ($expected) = pop @_;
+ my ($output) = pop @_;
+ my (%options) = @_;
+
+ my (@output) = get_core_output ($run, @$output);
+ fail "\u$run didn't produce any output" if !@output;
+
+ my $ignore_exit_codes = exists $options{IGNORE_EXIT_CODES};
+ if ($ignore_exit_codes) {
+ delete $options{IGNORE_EXIT_CODES};
+ @output = grep (!/^[a-zA-Z0-9-_]+: exit\(\-?\d+\)$/, @output);
+ }
+ my $ignore_user_faults = exists $options{IGNORE_USER_FAULTS};
+ if ($ignore_user_faults) {
+ delete $options{IGNORE_USER_FAULTS};
+ @output = grep (!/^Page fault at.*in user context\.$/
+ && !/: dying due to interrupt 0x0e \(.*\).$/
+ && !/^Interrupt 0x0e \(.*\) at eip=/
+ && !/^ cr2=.* error=.*/
+ && !/^ eax=.* ebx=.* ecx=.* edx=.*/
+ && !/^ esi=.* edi=.* esp=.* ebp=.*/
+ && !/^ cs=.* ds=.* es=.* ss=.*/, @output);
+ }
+ die "unknown option " . (keys (%options))[0] . "\n" if %options;
+
+ my ($msg);
+
+ # Compare actual output against each allowed output.
+ if (ref ($expected) eq 'ARRAY') {
+ my ($i) = 0;
+ $expected = {map ((++$i => $_), @$expected)};
+ }
+ foreach my $key (keys %$expected) {
+ my (@expected) = split ("\n", $expected->{$key});
+
+ $msg .= "Acceptable output:\n";
+ $msg .= join ('', map (" $_\n", @expected));
+
+ # Check whether actual and expected match.
+ # If it's a perfect match, we're done.
+ if ($#output == $#expected) {
+ my ($eq) = 1;
+ for (my ($i) = 0; $i <= $#expected; $i++) {
+ $eq = 0 if $output[$i] ne $expected[$i];
+ }
+ return $key if $eq;
+ }
+
+ # They differ. Output a diff.
+ my (@diff) = "";
+ my ($d) = Algorithm::Diff->new (\@expected, \@output);
+ while ($d->Next ()) {
+ my ($ef, $el, $af, $al) = $d->Get (qw (min1 max1 min2 max2));
+ if ($d->Same ()) {
+ push (@diff, map (" $_\n", $d->Items (1)));
+ } else {
+ push (@diff, map ("- $_\n", $d->Items (1))) if $d->Items (1);
+ push (@diff, map ("+ $_\n", $d->Items (2))) if $d->Items (2);
+ }
+ }
+
+ $msg .= "Differences in `diff -u' format:\n";
+ $msg .= join ('', @diff);
+ }
+
+ # Failed to match. Report failure.
+ $msg .= "\n(Process exit codes are excluded for matching purposes.)\n"
+ if $ignore_exit_codes;
+ $msg .= "\n(User fault messages are excluded for matching purposes.)\n"
+ if $ignore_user_faults;
+ fail "Test output failed to match any acceptable form.\n\n$msg";
+}
+
+# File system extraction.
+
+# check_archive (\%CONTENTS)
+#
+# Checks that the extracted file system's contents match \%CONTENTS.
+# Each key in the hash is a file name. Each value may be:
+#
+# - $FILE: Name of a host file containing the expected contents.
+#
+# - [$FILE, $OFFSET, $LENGTH]: An excerpt of host file $FILE
+# comprising the $LENGTH bytes starting at $OFFSET.
+#
+# - [$CONTENTS]: The literal expected file contents, as a string.
+#
+# - {SUBDIR}: A subdirectory, in the same form described here,
+# recursively.
+sub check_archive {
+ my ($expected_hier) = @_;
+
+ my (@output) = read_text_file ("$test.output");
+ common_checks ("file system extraction run", @output);
+
+ @output = get_core_output ("file system extraction run", @output);
+ @output = grep (!/^[a-zA-Z0-9-_]+: exit\(\d+\)$/, @output);
+ fail join ("\n", "Error extracting file system:", @output) if @output;
+
+ my ($test_base_name) = $test;
+ $test_base_name =~ s%.*/%%;
+ $test_base_name =~ s%-persistence$%%;
+ $expected_hier->{$test_base_name} = $prereq_tests[0];
+ $expected_hier->{'tar'} = 'tests/filesys/extended/tar';
+
+ my (%expected) = normalize_fs (flatten_hierarchy ($expected_hier, ""));
+ my (%actual) = read_tar ("$prereq_tests[0].tar");
+
+ my ($errors) = 0;
+ foreach my $name (sort keys %expected) {
+ if (exists $actual{$name}) {
+ if (is_dir ($actual{$name}) && !is_dir ($expected{$name})) {
+ print "$name is a directory but should be an ordinary file.\n";
+ $errors++;
+ } elsif (!is_dir ($actual{$name}) && is_dir ($expected{$name})) {
+ print "$name is an ordinary file but should be a directory.\n";
+ $errors++;
+ }
+ } else {
+ print "$name is missing from the file system.\n";
+ $errors++;
+ }
+ }
+ foreach my $name (sort keys %actual) {
+ if (!exists $expected{$name}) {
+ if ($name =~ /^[[:print:]]+$/) {
+ print "$name exists in the file system but it should not.\n";
+ } else {
+ my ($esc_name) = $name;
+ $esc_name =~ s/[^[:print:]]/./g;
+ print <<EOF;
+$esc_name exists in the file system but should not. (The name
+of this file contains unusual characters that were printed as `.'.)
+EOF
+ }
+ $errors++;
+ }
+ }
+ if ($errors) {
+ print "\nActual contents of file system:\n";
+ print_fs (%actual);
+ print "\nExpected contents of file system:\n";
+ print_fs (%expected);
+ } else {
+ foreach my $name (sort keys %expected) {
+ if (!is_dir ($expected{$name})) {
+ my ($exp_file, $exp_length) = open_file ($expected{$name});
+ my ($act_file, $act_length) = open_file ($actual{$name});
+ $errors += !compare_files ($exp_file, $exp_length,
+ $act_file, $act_length, $name,
+ !$errors);
+ close ($exp_file);
+ close ($act_file);
+ }
+ }
+ }
+ fail "Extracted file system contents are not correct.\n" if $errors;
+}
+
+# open_file ([$FILE, $OFFSET, $LENGTH])
+# open_file ([$CONTENTS])
+#
+# Opens a file for the contents passed in, which must be in one of
+# the two above forms that correspond to check_archive() arguments.
+#
+# Returns ($HANDLE, $LENGTH), where $HANDLE is the file's handle and
+# $LENGTH is the number of bytes in the file's content.
+sub open_file {
+ my ($value) = @_;
+ die if ref ($value) ne 'ARRAY';
+
+ my ($file) = tempfile ();
+ my ($length);
+ if (@$value == 1) {
+ $length = length ($value->[0]);
+ $file = tempfile ();
+ syswrite ($file, $value->[0]) == $length
+ or die "writing temporary file: $!\n";
+ sysseek ($file, 0, SEEK_SET);
+ } elsif (@$value == 3) {
+ $length = $value->[2];
+ open ($file, '<', $value->[0]) or die "$value->[0]: open: $!\n";
+ die "$value->[0]: file is smaller than expected\n"
+ if -s $file < $value->[1] + $length;
+ sysseek ($file, $value->[1], SEEK_SET);
+ } else {
+ die;
+ }
+ return ($file, $length);
+}
+
+# compare_files ($A, $A_SIZE, $B, $B_SIZE, $NAME, $VERBOSE)
+#
+# Compares $A_SIZE bytes in $A to $B_SIZE bytes in $B.
+# ($A and $B are handles.)
+# If their contents differ, prints a brief message describing
+# the differences, using $NAME to identify the file.
+# The message contains more detail if $VERBOSE is nonzero.
+# Returns 1 if the contents are identical, 0 otherwise.
+sub compare_files {
+ my ($a, $a_size, $b, $b_size, $name, $verbose) = @_;
+ my ($ofs) = 0;
+ select(STDOUT);
+ for (;;) {
+ my ($a_amt) = $a_size >= 1024 ? 1024 : $a_size;
+ my ($b_amt) = $b_size >= 1024 ? 1024 : $b_size;
+ my ($a_data, $b_data);
+ if (!defined (sysread ($a, $a_data, $a_amt))
+ || !defined (sysread ($b, $b_data, $b_amt))) {
+ die "reading $name: $!\n";
+ }
+
+ my ($a_len) = length $a_data;
+ my ($b_len) = length $b_data;
+ last if $a_len == 0 && $b_len == 0;
+
+ if ($a_data ne $b_data) {
+ my ($min_len) = $a_len < $b_len ? $a_len : $b_len;
+ my ($diff_ofs);
+ for ($diff_ofs = 0; $diff_ofs < $min_len; $diff_ofs++) {
+ last if (substr ($a_data, $diff_ofs, 1)
+ ne substr ($b_data, $diff_ofs, 1));
+ }
+
+ printf "\nFile $name differs from expected "
+ . "starting at offset 0x%x.\n", $ofs + $diff_ofs;
+ if ($verbose ) {
+ print "Expected contents:\n";
+ hex_dump (substr ($a_data, $diff_ofs, 64), $ofs + $diff_ofs);
+ print "Actual contents:\n";
+ hex_dump (substr ($b_data, $diff_ofs, 64), $ofs + $diff_ofs);
+ }
+ return 0;
+ }
+
+ $ofs += $a_len;
+ $a_size -= $a_len;
+ $b_size -= $b_len;
+ }
+ return 1;
+}
+
+# hex_dump ($DATA, $OFS)
+#
+# Prints $DATA in hex and text formats.
+# The first byte of $DATA corresponds to logical offset $OFS
+# in whatever file the data comes from.
+sub hex_dump {
+ my ($data, $ofs) = @_;
+
+ if ($data eq '') {
+ printf " (File ends at offset %08x.)\n", $ofs;
+ return;
+ }
+
+ my ($per_line) = 16;
+ while ((my $size = length ($data)) > 0) {
+ my ($start) = $ofs % $per_line;
+ my ($end) = $per_line;
+ $end = $start + $size if $end - $start > $size;
+ my ($n) = $end - $start;
+
+ printf "0x%08x ", int ($ofs / $per_line) * $per_line;
+
+ # Hex version.
+ print " " x $start;
+ for my $i ($start...$end - 1) {
+ printf "%02x", ord (substr ($data, $i - $start, 1));
+ print $i == $per_line / 2 - 1 ? '-' : ' ';
+ }
+ print " " x ($per_line - $end);
+
+ # Character version.
+ my ($esc_data) = substr ($data, 0, $n);
+ $esc_data =~ s/[^[:print:]]/./g;
+ print "|", " " x $start, $esc_data, " " x ($per_line - $end), "|";
+
+ print "\n";
+
+ $data = substr ($data, $n);
+ $ofs += $n;
+ }
+}
+
+# print_fs (%FS)
+#
+# Prints a list of files in %FS, which must be a file system
+# as flattened by flatten_hierarchy() and normalized by
+# normalize_fs().
+sub print_fs {
+ my (%fs) = @_;
+ foreach my $name (sort keys %fs) {
+ my ($esc_name) = $name;
+ $esc_name =~ s/[^[:print:]]/./g;
+ print "$esc_name: ";
+ if (!is_dir ($fs{$name})) {
+ print +file_size ($fs{$name}), "-byte file";
+ } else {
+ print "directory";
+ }
+ print "\n";
+ }
+ print "(empty)\n" if !@_;
+}
+
+# normalize_fs (%FS)
+#
+# Takes a file system as flattened by flatten_hierarchy().
+# Returns a similar file system in which values of the form $FILE
+# are replaced by those of the form [$FILE, $OFFSET, $LENGTH].
+sub normalize_fs {
+ my (%fs) = @_;
+ foreach my $name (keys %fs) {
+ my ($value) = $fs{$name};
+ next if is_dir ($value) || ref ($value) ne '';
+ die "can't open $value\n" if !stat $value;
+ $fs{$name} = [$value, 0, -s _];
+ }
+ return %fs;
+}
+
+# is_dir ($VALUE)
+#
+# Takes a value like one in the hash returned by flatten_hierarchy()
+# and returns 1 if it represents a directory, 0 otherwise.
+sub is_dir {
+ my ($value) = @_;
+ return ref ($value) eq '' && $value eq 'directory';
+}
+
+# file_size ($VALUE)
+#
+# Takes a value like one in the hash returned by flatten_hierarchy()
+# and returns the size of the file it represents.
+sub file_size {
+ my ($value) = @_;
+ die if is_dir ($value);
+ die if ref ($value) ne 'ARRAY';
+ return @$value > 1 ? $value->[2] : length ($value->[0]);
+}
+
+# flatten_hierarchy ($HIER_FS, $PREFIX)
+#
+# Takes a file system in the format expected by check_archive() and
+# returns a "flattened" version in which file names include all parent
+# directory names and the value of directories is just "directory".
+sub flatten_hierarchy {
+ my (%hier_fs) = %{$_[0]};
+ my ($prefix) = $_[1];
+ my (%flat_fs);
+ for my $name (keys %hier_fs) {
+ my ($value) = $hier_fs{$name};
+ if (ref $value eq 'HASH') {
+ %flat_fs = (%flat_fs, flatten_hierarchy ($value, "$prefix$name/"));
+ $flat_fs{"$prefix$name"} = 'directory';
+ } else {
+ $flat_fs{"$prefix$name"} = $value;
+ }
+ }
+ return %flat_fs;
+}
+
+# read_tar ($ARCHIVE)
+#
+# Reads the ustar-format tar file in $ARCHIVE
+# and returns a flattened file system for it.
+sub read_tar {
+ my ($archive) = @_;
+ my (%content);
+ open (ARCHIVE, '<', $archive) or fail "$archive: open: $!\n";
+ for (;;) {
+ my ($header);
+ if ((my $retval = sysread (ARCHIVE, $header, 512)) != 512) {
+ fail "$archive: unexpected end of file\n" if $retval >= 0;
+ fail "$archive: read: $!\n";
+ }
+
+ last if $header eq "\0" x 512;
+
+ # Verify magic numbers.
+ if (substr ($header, 257, 6) ne "ustar\0"
+ || substr ($header, 263, 2) ne '00') {
+ fail "$archive: corrupt ustar header\n";
+ }
+
+ # Verify checksum.
+ my ($chksum) = oct (unpack ("Z*", substr ($header, 148, 8, ' ' x 8)));
+ my ($correct_chksum) = unpack ("%32a*", $header);
+ fail "$archive: bad header checksum\n" if $chksum != $correct_chksum;
+
+ # Get file name.
+ my ($name) = unpack ("Z100", $header);
+ my ($prefix) = unpack ("Z*", substr ($header, 345));
+ $name = "$prefix/$name" if $prefix ne '';
+ fail "$archive: contains file with empty name" if $name eq '';
+
+ # Get type.
+ my ($typeflag) = substr ($header, 156, 1);
+ $typeflag = '0' if $typeflag eq "\0";
+ fail "unknown file type '$typeflag'\n" if $typeflag !~ /[05]/;
+
+ # Get size.
+ my ($size) = oct (unpack ("Z*", substr ($header, 124, 12)));
+ fail "bad size $size\n" if $size < 0;
+ $size = 0 if $typeflag eq '5';
+
+ # Store content.
+ if (exists $content{$name}) {
+ fail "$archive: contains multiple entries for $name\n";
+ }
+ if ($typeflag eq '5') {
+ $content{$name} = 'directory';
+ } else {
+ my ($position) = sysseek (ARCHIVE, 0, SEEK_CUR);
+ $content{$name} = [$archive, $position, $size];
+ sysseek (ARCHIVE, int (($size + 511) / 512) * 512, SEEK_CUR);
+ }
+ }
+ close (ARCHIVE);
+ return %content;
+}
+
+# Utilities.
+
+sub fail {
+ finish ("FAIL", @_);
+}
+
+sub pass {
+ finish ("PASS", @_);
+}
+
+sub finish {
+ my ($verdict, @messages) = @_;
+
+ seek ($msg_file, 0, 0);
+ push (@messages, <$msg_file>);
+ close ($msg_file);
+ chomp (@messages);
+
+ my ($result_fn) = "$test.result";
+ open (RESULT, '>', $result_fn) or die "$result_fn: create: $!\n";
+ print RESULT "$verdict\n";
+ print RESULT "$_\n" foreach @messages;
+ close (RESULT);
+
+ if ($verdict eq 'PASS') {
+ print STDOUT "pass $test\n";
+ } else {
+ print STDOUT "FAIL $test\n";
+ }
+ print STDOUT "$_\n" foreach @messages;
+
+ exit 0;
+}
+
+sub read_text_file {
+ my ($file_name) = @_;
+ open (FILE, '<', $file_name) or die "$file_name: open: $!\n";
+ my (@content) = <FILE>;
+ chomp (@content);
+ close (FILE);
+ return @content;
+}
+
+1;
diff --git a/src/tests/threads/Grading b/src/tests/threads/Grading
new file mode 100644
index 0000000..c9be35f
--- /dev/null
+++ b/src/tests/threads/Grading
@@ -0,0 +1,6 @@
+# Percentage of the testing point total designated for each set of
+# tests.
+
+20.0% tests/threads/Rubric.alarm
+40.0% tests/threads/Rubric.priority
+40.0% tests/threads/Rubric.mlfqs
diff --git a/src/tests/threads/Make.tests b/src/tests/threads/Make.tests
new file mode 100644
index 0000000..961c3ce
--- /dev/null
+++ b/src/tests/threads/Make.tests
@@ -0,0 +1,49 @@
+# -*- makefile -*-
+
+# Test names.
+tests/threads_TESTS = $(addprefix tests/threads/,alarm-single \
+alarm-multiple alarm-simultaneous alarm-zero alarm-negative \
+)
+
+# Sources for tests.
+tests/threads_SRC = tests/threads/tests.c
+tests/threads_SRC += tests/threads/alarm-wait.c
+tests/threads_SRC += tests/threads/alarm-simultaneous.c
+tests/threads_SRC += tests/threads/alarm-priority.c
+tests/threads_SRC += tests/threads/alarm-zero.c
+tests/threads_SRC += tests/threads/alarm-negative.c
+tests/threads_SRC += tests/threads/priority-change.c
+tests/threads_SRC += tests/threads/priority-donate-one.c
+tests/threads_SRC += tests/threads/priority-donate-multiple.c
+tests/threads_SRC += tests/threads/priority-donate-multiple2.c
+tests/threads_SRC += tests/threads/priority-donate-nest.c
+tests/threads_SRC += tests/threads/priority-donate-sema.c
+tests/threads_SRC += tests/threads/priority-donate-lower.c
+tests/threads_SRC += tests/threads/priority-fifo.c
+tests/threads_SRC += tests/threads/priority-preempt.c
+tests/threads_SRC += tests/threads/priority-sema.c
+tests/threads_SRC += tests/threads/priority-condvar.c
+tests/threads_SRC += tests/threads/priority-donate-chain.c
+tests/threads_SRC += tests/threads/mlfqs-load-1.c
+tests/threads_SRC += tests/threads/mlfqs-load-60.c
+tests/threads_SRC += tests/threads/mlfqs-load-avg.c
+tests/threads_SRC += tests/threads/mlfqs-recent-1.c
+tests/threads_SRC += tests/threads/mlfqs-fair.c
+tests/threads_SRC += tests/threads/mlfqs-block.c
+tests/threads_SRC += tests/threads/threadtest.c
+tests/threads_SRC += tests/threads/simplethreadtest.c
+
+MLFQS_OUTPUTS = \
+tests/threads/mlfqs-load-1.output \
+tests/threads/mlfqs-load-60.output \
+tests/threads/mlfqs-load-avg.output \
+tests/threads/mlfqs-recent-1.output \
+tests/threads/mlfqs-fair-2.output \
+tests/threads/mlfqs-fair-20.output \
+tests/threads/mlfqs-nice-2.output \
+tests/threads/mlfqs-nice-10.output \
+tests/threads/mlfqs-block.output
+
+$(MLFQS_OUTPUTS): KERNELFLAGS += -mlfqs
+$(MLFQS_OUTPUTS): TIMEOUT = 480
+
diff --git a/src/tests/threads/Rubric.alarm b/src/tests/threads/Rubric.alarm
new file mode 100644
index 0000000..61abe85
--- /dev/null
+++ b/src/tests/threads/Rubric.alarm
@@ -0,0 +1,8 @@
+Functionality and robustness of alarm clock:
+4 alarm-single
+4 alarm-multiple
+4 alarm-simultaneous
+4 alarm-priority
+
+1 alarm-zero
+1 alarm-negative
diff --git a/src/tests/threads/Rubric.mlfqs b/src/tests/threads/Rubric.mlfqs
new file mode 100644
index 0000000..f260091
--- /dev/null
+++ b/src/tests/threads/Rubric.mlfqs
@@ -0,0 +1,14 @@
+Functionality of advanced scheduler:
+5 mlfqs-load-1
+5 mlfqs-load-60
+3 mlfqs-load-avg
+
+5 mlfqs-recent-1
+
+5 mlfqs-fair-2
+3 mlfqs-fair-20
+
+4 mlfqs-nice-2
+2 mlfqs-nice-10
+
+5 mlfqs-block
diff --git a/src/tests/threads/Rubric.priority b/src/tests/threads/Rubric.priority
new file mode 100644
index 0000000..652bc99
--- /dev/null
+++ b/src/tests/threads/Rubric.priority
@@ -0,0 +1,15 @@
+Functionality of priority scheduler:
+3 priority-change
+3 priority-preempt
+
+3 priority-fifo
+3 priority-sema
+3 priority-condvar
+
+3 priority-donate-one
+3 priority-donate-multiple
+3 priority-donate-multiple2
+3 priority-donate-nest
+5 priority-donate-chain
+3 priority-donate-sema
+3 priority-donate-lower
diff --git a/src/tests/threads/alarm-multiple.ck b/src/tests/threads/alarm-multiple.ck
new file mode 100644
index 0000000..fd83bcd
--- /dev/null
+++ b/src/tests/threads/alarm-multiple.ck
@@ -0,0 +1,4 @@
+# -*- perl -*-
+use tests::tests;
+use tests::threads::alarm;
+check_alarm (7);
diff --git a/src/tests/threads/alarm-negative.c b/src/tests/threads/alarm-negative.c
new file mode 100644
index 0000000..aec52cf
--- /dev/null
+++ b/src/tests/threads/alarm-negative.c
@@ -0,0 +1,15 @@
+/* Tests timer_sleep(-100). Only requirement is that it not crash. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "devices/timer.h"
+
+void
+test_alarm_negative (void)
+{
+ timer_sleep (-100);
+ pass ();
+}
diff --git a/src/tests/threads/alarm-negative.ck b/src/tests/threads/alarm-negative.ck
new file mode 100644
index 0000000..0d2bab0
--- /dev/null
+++ b/src/tests/threads/alarm-negative.ck
@@ -0,0 +1,10 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(alarm-negative) begin
+(alarm-negative) PASS
+(alarm-negative) end
+EOF
+pass;
diff --git a/src/tests/threads/alarm-priority.c b/src/tests/threads/alarm-priority.c
new file mode 100644
index 0000000..2288ff6
--- /dev/null
+++ b/src/tests/threads/alarm-priority.c
@@ -0,0 +1,58 @@
+/* Checks that when the alarm clock wakes up threads, the
+ higher-priority threads run first. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "devices/timer.h"
+
+static thread_func alarm_priority_thread;
+static int64_t wake_time;
+static struct semaphore wait_sema;
+
+void
+test_alarm_priority (void)
+{
+ int i;
+
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ wake_time = timer_ticks () + 5 * TIMER_FREQ;
+ sema_init (&wait_sema, 0);
+
+ for (i = 0; i < 10; i++)
+ {
+ int priority = PRI_DEFAULT - (i + 5) % 10 - 1;
+ char name[16];
+ snprintf (name, sizeof name, "priority %d", priority);
+ thread_create (name, priority, alarm_priority_thread, NULL);
+ }
+
+ thread_set_priority (PRI_MIN);
+
+ for (i = 0; i < 10; i++)
+ sema_down (&wait_sema);
+}
+
+static void
+alarm_priority_thread (void *aux UNUSED)
+{
+ /* Busy-wait until the current time changes. */
+ int64_t start_time = timer_ticks ();
+ while (timer_elapsed (start_time) == 0)
+ continue;
+
+ /* Now we know we're at the very beginning of a timer tick, so
+ we can call timer_sleep() without worrying about races
+ between checking the time and a timer interrupt. */
+ timer_sleep (wake_time - timer_ticks ());
+
+ /* Print a message on wake-up. */
+ msg ("Thread %s woke up.", thread_name ());
+
+ sema_up (&wait_sema);
+}
diff --git a/src/tests/threads/alarm-priority.ck b/src/tests/threads/alarm-priority.ck
new file mode 100644
index 0000000..b57c78b
--- /dev/null
+++ b/src/tests/threads/alarm-priority.ck
@@ -0,0 +1,19 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(alarm-priority) begin
+(alarm-priority) Thread priority 30 woke up.
+(alarm-priority) Thread priority 29 woke up.
+(alarm-priority) Thread priority 28 woke up.
+(alarm-priority) Thread priority 27 woke up.
+(alarm-priority) Thread priority 26 woke up.
+(alarm-priority) Thread priority 25 woke up.
+(alarm-priority) Thread priority 24 woke up.
+(alarm-priority) Thread priority 23 woke up.
+(alarm-priority) Thread priority 22 woke up.
+(alarm-priority) Thread priority 21 woke up.
+(alarm-priority) end
+EOF
+pass;
diff --git a/src/tests/threads/alarm-simultaneous.c b/src/tests/threads/alarm-simultaneous.c
new file mode 100644
index 0000000..844eea4
--- /dev/null
+++ b/src/tests/threads/alarm-simultaneous.c
@@ -0,0 +1,94 @@
+/* Creates N threads, each of which sleeps a different, fixed
+ duration, M times. Records the wake-up order and verifies
+ that it is valid. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "devices/timer.h"
+
+static void test_sleep (int thread_cnt, int iterations);
+
+void
+test_alarm_simultaneous (void)
+{
+ test_sleep (3, 5);
+}
+
+/* Information about the test. */
+struct sleep_test
+ {
+ int64_t start; /* Current time at start of test. */
+ int iterations; /* Number of iterations per thread. */
+ int *output_pos; /* Current position in output buffer. */
+ };
+
+static void sleeper (void *);
+
+/* Runs THREAD_CNT threads thread sleep ITERATIONS times each. */
+static void
+test_sleep (int thread_cnt, int iterations)
+{
+ struct sleep_test test;
+ int *output;
+ int i;
+
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ msg ("Creating %d threads to sleep %d times each.", thread_cnt, iterations);
+ msg ("Each thread sleeps 10 ticks each time.");
+ msg ("Within an iteration, all threads should wake up on the same tick.");
+
+ /* Allocate memory. */
+ output = malloc (sizeof *output * iterations * thread_cnt * 2);
+ if (output == NULL)
+ PANIC ("couldn't allocate memory for test");
+
+ /* Initialize test. */
+ test.start = timer_ticks () + 100;
+ test.iterations = iterations;
+ test.output_pos = output;
+
+ /* Start threads. */
+ ASSERT (output != NULL);
+ for (i = 0; i < thread_cnt; i++)
+ {
+ char name[16];
+ snprintf (name, sizeof name, "thread %d", i);
+ thread_create (name, PRI_DEFAULT, sleeper, &test);
+ }
+
+ /* Wait long enough for all the threads to finish. */
+ timer_sleep (100 + iterations * 10 + 100);
+
+ /* Print completion order. */
+ msg ("iteration 0, thread 0: woke up after %d ticks", output[0]);
+ for (i = 1; i < test.output_pos - output; i++)
+ msg ("iteration %d, thread %d: woke up %d ticks later",
+ i / thread_cnt, i % thread_cnt, output[i] - output[i - 1]);
+
+ free (output);
+}
+
+/* Sleeper thread. */
+static void
+sleeper (void *test_)
+{
+ struct sleep_test *test = test_;
+ int i;
+
+ /* Make sure we're at the beginning of a timer tick. */
+ timer_sleep (1);
+
+ for (i = 1; i <= test->iterations; i++)
+ {
+ int64_t sleep_until = test->start + i * 10;
+ timer_sleep (sleep_until - timer_ticks ());
+ *test->output_pos++ = timer_ticks () - test->start;
+ thread_yield ();
+ }
+}
diff --git a/src/tests/threads/alarm-simultaneous.ck b/src/tests/threads/alarm-simultaneous.ck
new file mode 100644
index 0000000..406b8b0
--- /dev/null
+++ b/src/tests/threads/alarm-simultaneous.ck
@@ -0,0 +1,27 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(alarm-simultaneous) begin
+(alarm-simultaneous) Creating 3 threads to sleep 5 times each.
+(alarm-simultaneous) Each thread sleeps 10 ticks each time.
+(alarm-simultaneous) Within an iteration, all threads should wake up on the same tick.
+(alarm-simultaneous) iteration 0, thread 0: woke up after 10 ticks
+(alarm-simultaneous) iteration 0, thread 1: woke up 0 ticks later
+(alarm-simultaneous) iteration 0, thread 2: woke up 0 ticks later
+(alarm-simultaneous) iteration 1, thread 0: woke up 10 ticks later
+(alarm-simultaneous) iteration 1, thread 1: woke up 0 ticks later
+(alarm-simultaneous) iteration 1, thread 2: woke up 0 ticks later
+(alarm-simultaneous) iteration 2, thread 0: woke up 10 ticks later
+(alarm-simultaneous) iteration 2, thread 1: woke up 0 ticks later
+(alarm-simultaneous) iteration 2, thread 2: woke up 0 ticks later
+(alarm-simultaneous) iteration 3, thread 0: woke up 10 ticks later
+(alarm-simultaneous) iteration 3, thread 1: woke up 0 ticks later
+(alarm-simultaneous) iteration 3, thread 2: woke up 0 ticks later
+(alarm-simultaneous) iteration 4, thread 0: woke up 10 ticks later
+(alarm-simultaneous) iteration 4, thread 1: woke up 0 ticks later
+(alarm-simultaneous) iteration 4, thread 2: woke up 0 ticks later
+(alarm-simultaneous) end
+EOF
+pass;
diff --git a/src/tests/threads/alarm-single.ck b/src/tests/threads/alarm-single.ck
new file mode 100644
index 0000000..31215df
--- /dev/null
+++ b/src/tests/threads/alarm-single.ck
@@ -0,0 +1,4 @@
+# -*- perl -*-
+use tests::tests;
+use tests::threads::alarm;
+check_alarm (1);
diff --git a/src/tests/threads/alarm-wait.c b/src/tests/threads/alarm-wait.c
new file mode 100644
index 0000000..37d3afc
--- /dev/null
+++ b/src/tests/threads/alarm-wait.c
@@ -0,0 +1,152 @@
+/* Creates N threads, each of which sleeps a different, fixed
+ duration, M times. Records the wake-up order and verifies
+ that it is valid. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "devices/timer.h"
+
+static void test_sleep (int thread_cnt, int iterations);
+
+void
+test_alarm_single (void)
+{
+ test_sleep (5, 1);
+}
+
+void
+test_alarm_multiple (void)
+{
+ test_sleep (5, 7);
+}
+
+/* Information about the test. */
+struct sleep_test
+ {
+ int64_t start; /* Current time at start of test. */
+ int iterations; /* Number of iterations per thread. */
+
+ /* Output. */
+ struct lock output_lock; /* Lock protecting output buffer. */
+ int *output_pos; /* Current position in output buffer. */
+ };
+
+/* Information about an individual thread in the test. */
+struct sleep_thread
+ {
+ struct sleep_test *test; /* Info shared between all threads. */
+ int id; /* Sleeper ID. */
+ int duration; /* Number of ticks to sleep. */
+ int iterations; /* Iterations counted so far. */
+ };
+
+static void sleeper (void *);
+
+/* Runs THREAD_CNT threads thread sleep ITERATIONS times each. */
+static void
+test_sleep (int thread_cnt, int iterations)
+{
+ struct sleep_test test;
+ struct sleep_thread *threads;
+ int *output, *op;
+ int product;
+ int i;
+
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ msg ("Creating %d threads to sleep %d times each.", thread_cnt, iterations);
+ msg ("Thread 0 sleeps 10 ticks each time,");
+ msg ("thread 1 sleeps 20 ticks each time, and so on.");
+ msg ("If successful, product of iteration count and");
+ msg ("sleep duration will appear in nondescending order.");
+
+ /* Allocate memory. */
+ threads = malloc (sizeof *threads * thread_cnt);
+ output = malloc (sizeof *output * iterations * thread_cnt * 2);
+ if (threads == NULL || output == NULL)
+ PANIC ("couldn't allocate memory for test");
+
+ /* Initialize test. */
+ test.start = timer_ticks () + 100;
+ test.iterations = iterations;
+ lock_init (&test.output_lock);
+ test.output_pos = output;
+
+ /* Start threads. */
+ ASSERT (output != NULL);
+ for (i = 0; i < thread_cnt; i++)
+ {
+ struct sleep_thread *t = threads + i;
+ char name[16];
+
+ t->test = &test;
+ t->id = i;
+ t->duration = (i + 1) * 10;
+ t->iterations = 0;
+
+ snprintf (name, sizeof name, "thread %d", i);
+ thread_create (name, PRI_DEFAULT, sleeper, t);
+ }
+
+ /* Wait long enough for all the threads to finish. */
+ timer_sleep (100 + thread_cnt * iterations * 10 + 100);
+
+ /* Acquire the output lock in case some rogue thread is still
+ running. */
+ lock_acquire (&test.output_lock);
+
+ /* Print completion order. */
+ product = 0;
+ for (op = output; op < test.output_pos; op++)
+ {
+ struct sleep_thread *t;
+ int new_prod;
+
+ ASSERT (*op >= 0 && *op < thread_cnt);
+ t = threads + *op;
+
+ new_prod = ++t->iterations * t->duration;
+
+ msg ("thread %d: duration=%d, iteration=%d, product=%d",
+ t->id, t->duration, t->iterations, new_prod);
+
+ if (new_prod >= product)
+ product = new_prod;
+ else
+ fail ("thread %d woke up out of order (%d > %d)!",
+ t->id, product, new_prod);
+ }
+
+ /* Verify that we had the proper number of wakeups. */
+ for (i = 0; i < thread_cnt; i++)
+ if (threads[i].iterations != iterations)
+ fail ("thread %d woke up %d times instead of %d",
+ i, threads[i].iterations, iterations);
+
+ lock_release (&test.output_lock);
+ free (output);
+ free (threads);
+}
+
+/* Sleeper thread. */
+static void
+sleeper (void *t_)
+{
+ struct sleep_thread *t = t_;
+ struct sleep_test *test = t->test;
+ int i;
+
+ for (i = 1; i <= test->iterations; i++)
+ {
+ int64_t sleep_until = test->start + i * t->duration;
+ timer_sleep (sleep_until - timer_ticks ());
+ lock_acquire (&test->output_lock);
+ *test->output_pos++ = t->id;
+ lock_release (&test->output_lock);
+ }
+}
diff --git a/src/tests/threads/alarm-zero.c b/src/tests/threads/alarm-zero.c
new file mode 100644
index 0000000..c8a3ee2
--- /dev/null
+++ b/src/tests/threads/alarm-zero.c
@@ -0,0 +1,15 @@
+/* Tests timer_sleep(0), which should return immediately. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "devices/timer.h"
+
+void
+test_alarm_zero (void)
+{
+ timer_sleep (0);
+ pass ();
+}
diff --git a/src/tests/threads/alarm-zero.ck b/src/tests/threads/alarm-zero.ck
new file mode 100644
index 0000000..a6b1a3c
--- /dev/null
+++ b/src/tests/threads/alarm-zero.ck
@@ -0,0 +1,10 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(alarm-zero) begin
+(alarm-zero) PASS
+(alarm-zero) end
+EOF
+pass;
diff --git a/src/tests/threads/alarm.pm b/src/tests/threads/alarm.pm
new file mode 100644
index 0000000..84b3b7f
--- /dev/null
+++ b/src/tests/threads/alarm.pm
@@ -0,0 +1,32 @@
+sub check_alarm {
+ my ($iterations) = @_;
+ our ($test);
+
+ @output = read_text_file ("$test.output");
+ common_checks ("run", @output);
+
+ my (@products);
+ for (my ($i) = 0; $i < $iterations; $i++) {
+ for (my ($t) = 0; $t < 5; $t++) {
+ push (@products, ($i + 1) * ($t + 1) * 10);
+ }
+ }
+ @products = sort {$a <=> $b} @products;
+
+ local ($_);
+ foreach (@output) {
+ fail $_ if /out of order/i;
+
+ my ($p) = /product=(\d+)$/;
+ next if !defined $p;
+
+ my ($q) = shift (@products);
+ fail "Too many wakeups.\n" if !defined $q;
+ fail "Out of order wakeups ($p vs. $q).\n" if $p != $q; # FIXME
+ }
+ fail scalar (@products) . " fewer wakeups than expected.\n"
+ if @products != 0;
+ pass;
+}
+
+1;
diff --git a/src/tests/threads/mlfqs-block.c b/src/tests/threads/mlfqs-block.c
new file mode 100644
index 0000000..6d4992d
--- /dev/null
+++ b/src/tests/threads/mlfqs-block.c
@@ -0,0 +1,64 @@
+/* Checks that recent_cpu and priorities are updated for blocked
+ threads.
+
+ The main thread sleeps for 25 seconds, spins for 5 seconds,
+ then releases a lock. The "block" thread spins for 20 seconds
+ then attempts to acquire the lock, which will block for 10
+ seconds (until the main thread releases it). If recent_cpu
+ decays properly while the "block" thread sleeps, then the
+ block thread should be immediately scheduled when the main
+ thread releases the lock. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "devices/timer.h"
+
+static void block_thread (void *lock_);
+
+void
+test_mlfqs_block (void)
+{
+ int64_t start_time;
+ struct lock lock;
+
+ ASSERT (thread_mlfqs);
+
+ msg ("Main thread acquiring lock.");
+ lock_init (&lock);
+ lock_acquire (&lock);
+
+ msg ("Main thread creating block thread, sleeping 25 seconds...");
+ thread_create ("block", PRI_DEFAULT, block_thread, &lock);
+ timer_sleep (25 * TIMER_FREQ);
+
+ msg ("Main thread spinning for 5 seconds...");
+ start_time = timer_ticks ();
+ while (timer_elapsed (start_time) < 5 * TIMER_FREQ)
+ continue;
+
+ msg ("Main thread releasing lock.");
+ lock_release (&lock);
+
+ msg ("Block thread should have already acquired lock.");
+}
+
+static void
+block_thread (void *lock_)
+{
+ struct lock *lock = lock_;
+ int64_t start_time;
+
+ msg ("Block thread spinning for 20 seconds...");
+ start_time = timer_ticks ();
+ while (timer_elapsed (start_time) < 20 * TIMER_FREQ)
+ continue;
+
+ msg ("Block thread acquiring lock...");
+ lock_acquire (lock);
+
+ msg ("...got it.");
+}
diff --git a/src/tests/threads/mlfqs-block.ck b/src/tests/threads/mlfqs-block.ck
new file mode 100644
index 0000000..8833a3a
--- /dev/null
+++ b/src/tests/threads/mlfqs-block.ck
@@ -0,0 +1,17 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(mlfqs-block) begin
+(mlfqs-block) Main thread acquiring lock.
+(mlfqs-block) Main thread creating block thread, sleeping 25 seconds...
+(mlfqs-block) Block thread spinning for 20 seconds...
+(mlfqs-block) Block thread acquiring lock...
+(mlfqs-block) Main thread spinning for 5 seconds...
+(mlfqs-block) Main thread releasing lock.
+(mlfqs-block) ...got it.
+(mlfqs-block) Block thread should have already acquired lock.
+(mlfqs-block) end
+EOF
+pass;
diff --git a/src/tests/threads/mlfqs-fair-2.ck b/src/tests/threads/mlfqs-fair-2.ck
new file mode 100644
index 0000000..5b19ff1
--- /dev/null
+++ b/src/tests/threads/mlfqs-fair-2.ck
@@ -0,0 +1,7 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::threads::mlfqs;
+
+check_mlfqs_fair ([0, 0], 50);
diff --git a/src/tests/threads/mlfqs-fair-20.ck b/src/tests/threads/mlfqs-fair-20.ck
new file mode 100644
index 0000000..bb4d051
--- /dev/null
+++ b/src/tests/threads/mlfqs-fair-20.ck
@@ -0,0 +1,7 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::threads::mlfqs;
+
+check_mlfqs_fair ([(0) x 20], 20);
diff --git a/src/tests/threads/mlfqs-fair.c b/src/tests/threads/mlfqs-fair.c
new file mode 100644
index 0000000..3b1bea5
--- /dev/null
+++ b/src/tests/threads/mlfqs-fair.c
@@ -0,0 +1,124 @@
+/* Measures the correctness of the "nice" implementation.
+
+ The "fair" tests run either 2 or 20 threads all niced to 0.
+ The threads should all receive approximately the same number
+ of ticks. Each test runs for 30 seconds, so the ticks should
+ also sum to approximately 30 * 100 == 3000 ticks.
+
+ The mlfqs-nice-2 test runs 2 threads, one with nice 0, the
+ other with nice 5, which should receive 1,904 and 1,096 ticks,
+ respectively, over 30 seconds.
+
+ The mlfqs-nice-10 test runs 10 threads with nice 0 through 9.
+ They should receive 672, 588, 492, 408, 316, 232, 152, 92, 40,
+ and 8 ticks, respectively, over 30 seconds.
+
+ (The above are computed via simulation in mlfqs.pm.) */
+
+#include <stdio.h>
+#include <inttypes.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/palloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "devices/timer.h"
+
+static void test_mlfqs_fair (int thread_cnt, int nice_min, int nice_step);
+
+void
+test_mlfqs_fair_2 (void)
+{
+ test_mlfqs_fair (2, 0, 0);
+}
+
+void
+test_mlfqs_fair_20 (void)
+{
+ test_mlfqs_fair (20, 0, 0);
+}
+
+void
+test_mlfqs_nice_2 (void)
+{
+ test_mlfqs_fair (2, 0, 5);
+}
+
+void
+test_mlfqs_nice_10 (void)
+{
+ test_mlfqs_fair (10, 0, 1);
+}
+
+#define MAX_THREAD_CNT 20
+
+struct thread_info
+ {
+ int64_t start_time;
+ int tick_count;
+ int nice;
+ };
+
+static void load_thread (void *aux);
+
+static void
+test_mlfqs_fair (int thread_cnt, int nice_min, int nice_step)
+{
+ struct thread_info info[MAX_THREAD_CNT];
+ int64_t start_time;
+ int nice;
+ int i;
+
+ ASSERT (thread_mlfqs);
+ ASSERT (thread_cnt <= MAX_THREAD_CNT);
+ ASSERT (nice_min >= -10);
+ ASSERT (nice_step >= 0);
+ ASSERT (nice_min + nice_step * (thread_cnt - 1) <= 20);
+
+ thread_set_nice (-20);
+
+ start_time = timer_ticks ();
+ msg ("Starting %d threads...", thread_cnt);
+ nice = nice_min;
+ for (i = 0; i < thread_cnt; i++)
+ {
+ struct thread_info *ti = &info[i];
+ char name[16];
+
+ ti->start_time = start_time;
+ ti->tick_count = 0;
+ ti->nice = nice;
+
+ snprintf(name, sizeof name, "load %d", i);
+ thread_create (name, PRI_DEFAULT, load_thread, ti);
+
+ nice += nice_step;
+ }
+ msg ("Starting threads took %"PRId64" ticks.", timer_elapsed (start_time));
+
+ msg ("Sleeping 40 seconds to let threads run, please wait...");
+ timer_sleep (40 * TIMER_FREQ);
+
+ for (i = 0; i < thread_cnt; i++)
+ msg ("Thread %d received %d ticks.", i, info[i].tick_count);
+}
+
+static void
+load_thread (void *ti_)
+{
+ struct thread_info *ti = ti_;
+ int64_t sleep_time = 5 * TIMER_FREQ;
+ int64_t spin_time = sleep_time + 30 * TIMER_FREQ;
+ int64_t last_time = 0;
+
+ thread_set_nice (ti->nice);
+ timer_sleep (sleep_time - timer_elapsed (ti->start_time));
+ while (timer_elapsed (ti->start_time) < spin_time)
+ {
+ int64_t cur_time = timer_ticks ();
+ if (cur_time != last_time)
+ ti->tick_count++;
+ last_time = cur_time;
+ }
+}
diff --git a/src/tests/threads/mlfqs-load-1.c b/src/tests/threads/mlfqs-load-1.c
new file mode 100644
index 0000000..a39eea2
--- /dev/null
+++ b/src/tests/threads/mlfqs-load-1.c
@@ -0,0 +1,60 @@
+/* Verifies that a single busy thread raises the load average to
+ 0.5 in 38 to 45 seconds. The expected time is 42 seconds, as
+ you can verify:
+ perl -e '$i++,$a=(59*$a+1)/60while$a<=.5;print "$i\n"'
+
+ Then, verifies that 10 seconds of inactivity drop the load
+ average back below 0.5 again. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "devices/timer.h"
+
+void
+test_mlfqs_load_1 (void)
+{
+ int64_t start_time;
+ int elapsed;
+ int load_avg;
+
+ ASSERT (thread_mlfqs);
+
+ msg ("spinning for up to 45 seconds, please wait...");
+
+ start_time = timer_ticks ();
+ for (;;)
+ {
+ load_avg = thread_get_load_avg ();
+ ASSERT (load_avg >= 0);
+ elapsed = timer_elapsed (start_time) / TIMER_FREQ;
+ if (load_avg > 100)
+ fail ("load average is %d.%02d "
+ "but should be between 0 and 1 (after %d seconds)",
+ load_avg / 100, load_avg % 100, elapsed);
+ else if (load_avg > 50)
+ break;
+ else if (elapsed > 45)
+ fail ("load average stayed below 0.5 for more than 45 seconds");
+ }
+
+ if (elapsed < 38)
+ fail ("load average took only %d seconds to rise above 0.5", elapsed);
+ msg ("load average rose to 0.5 after %d seconds", elapsed);
+
+ msg ("sleeping for another 10 seconds, please wait...");
+ timer_sleep (TIMER_FREQ * 10);
+
+ load_avg = thread_get_load_avg ();
+ if (load_avg < 0)
+ fail ("load average fell below 0");
+ if (load_avg > 50)
+ fail ("load average stayed above 0.5 for more than 10 seconds");
+ msg ("load average fell back below 0.5 (to %d.%02d)",
+ load_avg / 100, load_avg % 100);
+
+ pass ();
+}
diff --git a/src/tests/threads/mlfqs-load-1.ck b/src/tests/threads/mlfqs-load-1.ck
new file mode 100644
index 0000000..faf0ffa
--- /dev/null
+++ b/src/tests/threads/mlfqs-load-1.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);
+
+@output = get_core_output ("run", @output);
+fail "missing PASS in output"
+ unless grep ($_ eq '(mlfqs-load-1) PASS', @output);
+
+pass;
diff --git a/src/tests/threads/mlfqs-load-60.c b/src/tests/threads/mlfqs-load-60.c
new file mode 100644
index 0000000..b6a3eb6
--- /dev/null
+++ b/src/tests/threads/mlfqs-load-60.c
@@ -0,0 +1,155 @@
+/* Starts 60 threads that each sleep for 10 seconds, then spin in
+ a tight loop for 60 seconds, and sleep for another 60 seconds.
+ Every 2 seconds after the initial sleep, the main thread
+ prints the load average.
+
+ The expected output is this (some margin of error is allowed):
+
+ After 0 seconds, load average=1.00.
+ After 2 seconds, load average=2.95.
+ After 4 seconds, load average=4.84.
+ After 6 seconds, load average=6.66.
+ After 8 seconds, load average=8.42.
+ After 10 seconds, load average=10.13.
+ After 12 seconds, load average=11.78.
+ After 14 seconds, load average=13.37.
+ After 16 seconds, load average=14.91.
+ After 18 seconds, load average=16.40.
+ After 20 seconds, load average=17.84.
+ After 22 seconds, load average=19.24.
+ After 24 seconds, load average=20.58.
+ After 26 seconds, load average=21.89.
+ After 28 seconds, load average=23.15.
+ After 30 seconds, load average=24.37.
+ After 32 seconds, load average=25.54.
+ After 34 seconds, load average=26.68.
+ After 36 seconds, load average=27.78.
+ After 38 seconds, load average=28.85.
+ After 40 seconds, load average=29.88.
+ After 42 seconds, load average=30.87.
+ After 44 seconds, load average=31.84.
+ After 46 seconds, load average=32.77.
+ After 48 seconds, load average=33.67.
+ After 50 seconds, load average=34.54.
+ After 52 seconds, load average=35.38.
+ After 54 seconds, load average=36.19.
+ After 56 seconds, load average=36.98.
+ After 58 seconds, load average=37.74.
+ After 60 seconds, load average=37.48.
+ After 62 seconds, load average=36.24.
+ After 64 seconds, load average=35.04.
+ After 66 seconds, load average=33.88.
+ After 68 seconds, load average=32.76.
+ After 70 seconds, load average=31.68.
+ After 72 seconds, load average=30.63.
+ After 74 seconds, load average=29.62.
+ After 76 seconds, load average=28.64.
+ After 78 seconds, load average=27.69.
+ After 80 seconds, load average=26.78.
+ After 82 seconds, load average=25.89.
+ After 84 seconds, load average=25.04.
+ After 86 seconds, load average=24.21.
+ After 88 seconds, load average=23.41.
+ After 90 seconds, load average=22.64.
+ After 92 seconds, load average=21.89.
+ After 94 seconds, load average=21.16.
+ After 96 seconds, load average=20.46.
+ After 98 seconds, load average=19.79.
+ After 100 seconds, load average=19.13.
+ After 102 seconds, load average=18.50.
+ After 104 seconds, load average=17.89.
+ After 106 seconds, load average=17.30.
+ After 108 seconds, load average=16.73.
+ After 110 seconds, load average=16.17.
+ After 112 seconds, load average=15.64.
+ After 114 seconds, load average=15.12.
+ After 116 seconds, load average=14.62.
+ After 118 seconds, load average=14.14.
+ After 120 seconds, load average=13.67.
+ After 122 seconds, load average=13.22.
+ After 124 seconds, load average=12.78.
+ After 126 seconds, load average=12.36.
+ After 128 seconds, load average=11.95.
+ After 130 seconds, load average=11.56.
+ After 132 seconds, load average=11.17.
+ After 134 seconds, load average=10.80.
+ After 136 seconds, load average=10.45.
+ After 138 seconds, load average=10.10.
+ After 140 seconds, load average=9.77.
+ After 142 seconds, load average=9.45.
+ After 144 seconds, load average=9.13.
+ After 146 seconds, load average=8.83.
+ After 148 seconds, load average=8.54.
+ After 150 seconds, load average=8.26.
+ After 152 seconds, load average=7.98.
+ After 154 seconds, load average=7.72.
+ After 156 seconds, load average=7.47.
+ After 158 seconds, load average=7.22.
+ After 160 seconds, load average=6.98.
+ After 162 seconds, load average=6.75.
+ After 164 seconds, load average=6.53.
+ After 166 seconds, load average=6.31.
+ After 168 seconds, load average=6.10.
+ After 170 seconds, load average=5.90.
+ After 172 seconds, load average=5.70.
+ After 174 seconds, load average=5.52.
+ After 176 seconds, load average=5.33.
+ After 178 seconds, load average=5.16.
+*/
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "devices/timer.h"
+
+static int64_t start_time;
+
+static void load_thread (void *aux);
+
+#define THREAD_CNT 60
+
+void
+test_mlfqs_load_60 (void)
+{
+ int i;
+
+ ASSERT (thread_mlfqs);
+
+ start_time = timer_ticks ();
+ msg ("Starting %d niced load threads...", THREAD_CNT);
+ for (i = 0; i < THREAD_CNT; i++)
+ {
+ char name[16];
+ snprintf(name, sizeof name, "load %d", i);
+ thread_create (name, PRI_DEFAULT, load_thread, NULL);
+ }
+ msg ("Starting threads took %d seconds.",
+ timer_elapsed (start_time) / TIMER_FREQ);
+
+ for (i = 0; i < 90; i++)
+ {
+ int64_t sleep_until = start_time + TIMER_FREQ * (2 * i + 10);
+ int load_avg;
+ timer_sleep (sleep_until - timer_ticks ());
+ load_avg = thread_get_load_avg ();
+ msg ("After %d seconds, load average=%d.%02d.",
+ i * 2, load_avg / 100, load_avg % 100);
+ }
+}
+
+static void
+load_thread (void *aux UNUSED)
+{
+ int64_t sleep_time = 10 * TIMER_FREQ;
+ int64_t spin_time = sleep_time + 60 * TIMER_FREQ;
+ int64_t exit_time = spin_time + 60 * TIMER_FREQ;
+
+ thread_set_nice (20);
+ timer_sleep (sleep_time - timer_elapsed (start_time));
+ while (timer_elapsed (start_time) < spin_time)
+ continue;
+ timer_sleep (exit_time - timer_elapsed (start_time));
+}
diff --git a/src/tests/threads/mlfqs-load-60.ck b/src/tests/threads/mlfqs-load-60.ck
new file mode 100644
index 0000000..cb69220
--- /dev/null
+++ b/src/tests/threads/mlfqs-load-60.ck
@@ -0,0 +1,36 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::threads::mlfqs;
+
+our ($test);
+
+my (@output) = read_text_file ("$test.output");
+common_checks ("run", @output);
+@output = get_core_output ("run", @output);
+
+# Get actual values.
+local ($_);
+my (@actual);
+foreach (@output) {
+ my ($t, $load_avg) = /After (\d+) seconds, load average=(\d+\.\d+)\./
+ or next;
+ $actual[$t] = $load_avg;
+}
+
+# Calculate expected values.
+my ($load_avg) = 0;
+my ($recent) = 0;
+my (@expected);
+for (my ($t) = 0; $t < 180; $t++) {
+ my ($ready) = $t < 60 ? 60 : 0;
+ $load_avg = (59/60) * $load_avg + (1/60) * $ready;
+ $expected[$t] = $load_avg;
+}
+
+mlfqs_compare ("time", "%.2f", \@actual, \@expected, 3.5, [2, 178, 2],
+ "Some load average values were missing or "
+ . "differed from those expected "
+ . "by more than 3.5.");
+pass;
diff --git a/src/tests/threads/mlfqs-load-avg.c b/src/tests/threads/mlfqs-load-avg.c
new file mode 100644
index 0000000..50e83e2
--- /dev/null
+++ b/src/tests/threads/mlfqs-load-avg.c
@@ -0,0 +1,167 @@
+/* Starts 60 threads numbered 0 through 59. Thread #i sleeps for
+ (10+i) seconds, then spins in a loop for 60 seconds, then
+ sleeps until a total of 120 seconds have passed. Every 2
+ seconds, starting 10 seconds in, the main thread prints the
+ load average.
+
+ The expected output is listed below. Some margin of error is
+ allowed.
+
+ If your implementation fails this test but passes most other
+ tests, then consider whether you are doing too much work in
+ the timer interrupt. If the timer interrupt handler takes too
+ long, then the test's main thread will not have enough time to
+ do its own work (printing a message) and go back to sleep
+ before the next tick arrives. Then the main thread will be
+ ready, instead of sleeping, when the tick arrives,
+ artificially driving up the load average.
+
+ After 0 seconds, load average=0.00.
+ After 2 seconds, load average=0.05.
+ After 4 seconds, load average=0.16.
+ After 6 seconds, load average=0.34.
+ After 8 seconds, load average=0.58.
+ After 10 seconds, load average=0.87.
+ After 12 seconds, load average=1.22.
+ After 14 seconds, load average=1.63.
+ After 16 seconds, load average=2.09.
+ After 18 seconds, load average=2.60.
+ After 20 seconds, load average=3.16.
+ After 22 seconds, load average=3.76.
+ After 24 seconds, load average=4.42.
+ After 26 seconds, load average=5.11.
+ After 28 seconds, load average=5.85.
+ After 30 seconds, load average=6.63.
+ After 32 seconds, load average=7.46.
+ After 34 seconds, load average=8.32.
+ After 36 seconds, load average=9.22.
+ After 38 seconds, load average=10.15.
+ After 40 seconds, load average=11.12.
+ After 42 seconds, load average=12.13.
+ After 44 seconds, load average=13.16.
+ After 46 seconds, load average=14.23.
+ After 48 seconds, load average=15.33.
+ After 50 seconds, load average=16.46.
+ After 52 seconds, load average=17.62.
+ After 54 seconds, load average=18.81.
+ After 56 seconds, load average=20.02.
+ After 58 seconds, load average=21.26.
+ After 60 seconds, load average=22.52.
+ After 62 seconds, load average=23.71.
+ After 64 seconds, load average=24.80.
+ After 66 seconds, load average=25.78.
+ After 68 seconds, load average=26.66.
+ After 70 seconds, load average=27.45.
+ After 72 seconds, load average=28.14.
+ After 74 seconds, load average=28.75.
+ After 76 seconds, load average=29.27.
+ After 78 seconds, load average=29.71.
+ After 80 seconds, load average=30.06.
+ After 82 seconds, load average=30.34.
+ After 84 seconds, load average=30.55.
+ After 86 seconds, load average=30.68.
+ After 88 seconds, load average=30.74.
+ After 90 seconds, load average=30.73.
+ After 92 seconds, load average=30.66.
+ After 94 seconds, load average=30.52.
+ After 96 seconds, load average=30.32.
+ After 98 seconds, load average=30.06.
+ After 100 seconds, load average=29.74.
+ After 102 seconds, load average=29.37.
+ After 104 seconds, load average=28.95.
+ After 106 seconds, load average=28.47.
+ After 108 seconds, load average=27.94.
+ After 110 seconds, load average=27.36.
+ After 112 seconds, load average=26.74.
+ After 114 seconds, load average=26.07.
+ After 116 seconds, load average=25.36.
+ After 118 seconds, load average=24.60.
+ After 120 seconds, load average=23.81.
+ After 122 seconds, load average=23.02.
+ After 124 seconds, load average=22.26.
+ After 126 seconds, load average=21.52.
+ After 128 seconds, load average=20.81.
+ After 130 seconds, load average=20.12.
+ After 132 seconds, load average=19.46.
+ After 134 seconds, load average=18.81.
+ After 136 seconds, load average=18.19.
+ After 138 seconds, load average=17.59.
+ After 140 seconds, load average=17.01.
+ After 142 seconds, load average=16.45.
+ After 144 seconds, load average=15.90.
+ After 146 seconds, load average=15.38.
+ After 148 seconds, load average=14.87.
+ After 150 seconds, load average=14.38.
+ After 152 seconds, load average=13.90.
+ After 154 seconds, load average=13.44.
+ After 156 seconds, load average=13.00.
+ After 158 seconds, load average=12.57.
+ After 160 seconds, load average=12.15.
+ After 162 seconds, load average=11.75.
+ After 164 seconds, load average=11.36.
+ After 166 seconds, load average=10.99.
+ After 168 seconds, load average=10.62.
+ After 170 seconds, load average=10.27.
+ After 172 seconds, load average=9.93.
+ After 174 seconds, load average=9.61.
+ After 176 seconds, load average=9.29.
+ After 178 seconds, load average=8.98.
+*/
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "devices/timer.h"
+
+static int64_t start_time;
+
+static void load_thread (void *seq_no);
+
+#define THREAD_CNT 60
+
+void
+test_mlfqs_load_avg (void)
+{
+ int i;
+
+ ASSERT (thread_mlfqs);
+
+ start_time = timer_ticks ();
+ msg ("Starting %d load threads...", THREAD_CNT);
+ for (i = 0; i < THREAD_CNT; i++)
+ {
+ char name[16];
+ snprintf(name, sizeof name, "load %d", i);
+ thread_create (name, PRI_DEFAULT, load_thread, (void *) i);
+ }
+ msg ("Starting threads took %d seconds.",
+ timer_elapsed (start_time) / TIMER_FREQ);
+ thread_set_nice (-20);
+
+ for (i = 0; i < 90; i++)
+ {
+ int64_t sleep_until = start_time + TIMER_FREQ * (2 * i + 10);
+ int load_avg;
+ timer_sleep (sleep_until - timer_ticks ());
+ load_avg = thread_get_load_avg ();
+ msg ("After %d seconds, load average=%d.%02d.",
+ i * 2, load_avg / 100, load_avg % 100);
+ }
+}
+
+static void
+load_thread (void *seq_no_)
+{
+ int seq_no = (int) seq_no_;
+ int sleep_time = TIMER_FREQ * (10 + seq_no);
+ int spin_time = sleep_time + TIMER_FREQ * THREAD_CNT;
+ int exit_time = TIMER_FREQ * (THREAD_CNT * 2);
+
+ timer_sleep (sleep_time - timer_elapsed (start_time));
+ while (timer_elapsed (start_time) < spin_time)
+ continue;
+ timer_sleep (exit_time - timer_elapsed (start_time));
+}
diff --git a/src/tests/threads/mlfqs-load-avg.ck b/src/tests/threads/mlfqs-load-avg.ck
new file mode 100644
index 0000000..2254d05
--- /dev/null
+++ b/src/tests/threads/mlfqs-load-avg.ck
@@ -0,0 +1,36 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::threads::mlfqs;
+
+our ($test);
+my (@output) = read_text_file ("$test.output");
+
+common_checks ("run", @output);
+@output = get_core_output ("run", @output);
+
+# Get actual values.
+local ($_);
+my (@actual);
+foreach (@output) {
+ my ($t, $load_avg) = /After (\d+) seconds, load average=(\d+\.\d+)\./
+ or next;
+ $actual[$t] = $load_avg;
+}
+
+# Calculate expected values.
+my ($load_avg) = 0;
+my ($recent) = 0;
+my (@expected);
+for (my ($t) = 0; $t < 180; $t++) {
+ my ($ready) = $t < 60 ? $t : $t < 120 ? 120 - $t : 0;
+ $load_avg = (59/60) * $load_avg + (1/60) * $ready;
+ $expected[$t] = $load_avg;
+}
+
+mlfqs_compare ("time", "%.2f", \@actual, \@expected, 2.5, [2, 178, 2],
+ "Some load average values were missing or "
+ . "differed from those expected "
+ . "by more than 2.5.");
+pass;
diff --git a/src/tests/threads/mlfqs-nice-10.ck b/src/tests/threads/mlfqs-nice-10.ck
new file mode 100644
index 0000000..53e0abe
--- /dev/null
+++ b/src/tests/threads/mlfqs-nice-10.ck
@@ -0,0 +1,7 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::threads::mlfqs;
+
+check_mlfqs_fair ([0...9], 25);
diff --git a/src/tests/threads/mlfqs-nice-2.ck b/src/tests/threads/mlfqs-nice-2.ck
new file mode 100644
index 0000000..ada366b
--- /dev/null
+++ b/src/tests/threads/mlfqs-nice-2.ck
@@ -0,0 +1,7 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::threads::mlfqs;
+
+check_mlfqs_fair ([0, 5], 50);
diff --git a/src/tests/threads/mlfqs-recent-1.c b/src/tests/threads/mlfqs-recent-1.c
new file mode 100644
index 0000000..4258671
--- /dev/null
+++ b/src/tests/threads/mlfqs-recent-1.c
@@ -0,0 +1,144 @@
+/* Checks that recent_cpu is calculated properly for the case of
+ a single ready process.
+
+ The expected output is this (some margin of error is allowed):
+
+ After 2 seconds, recent_cpu is 6.40, load_avg is 0.03.
+ After 4 seconds, recent_cpu is 12.60, load_avg is 0.07.
+ After 6 seconds, recent_cpu is 18.61, load_avg is 0.10.
+ After 8 seconds, recent_cpu is 24.44, load_avg is 0.13.
+ After 10 seconds, recent_cpu is 30.08, load_avg is 0.15.
+ After 12 seconds, recent_cpu is 35.54, load_avg is 0.18.
+ After 14 seconds, recent_cpu is 40.83, load_avg is 0.21.
+ After 16 seconds, recent_cpu is 45.96, load_avg is 0.24.
+ After 18 seconds, recent_cpu is 50.92, load_avg is 0.26.
+ After 20 seconds, recent_cpu is 55.73, load_avg is 0.29.
+ After 22 seconds, recent_cpu is 60.39, load_avg is 0.31.
+ After 24 seconds, recent_cpu is 64.90, load_avg is 0.33.
+ After 26 seconds, recent_cpu is 69.27, load_avg is 0.35.
+ After 28 seconds, recent_cpu is 73.50, load_avg is 0.38.
+ After 30 seconds, recent_cpu is 77.60, load_avg is 0.40.
+ After 32 seconds, recent_cpu is 81.56, load_avg is 0.42.
+ After 34 seconds, recent_cpu is 85.40, load_avg is 0.44.
+ After 36 seconds, recent_cpu is 89.12, load_avg is 0.45.
+ After 38 seconds, recent_cpu is 92.72, load_avg is 0.47.
+ After 40 seconds, recent_cpu is 96.20, load_avg is 0.49.
+ After 42 seconds, recent_cpu is 99.57, load_avg is 0.51.
+ After 44 seconds, recent_cpu is 102.84, load_avg is 0.52.
+ After 46 seconds, recent_cpu is 106.00, load_avg is 0.54.
+ After 48 seconds, recent_cpu is 109.06, load_avg is 0.55.
+ After 50 seconds, recent_cpu is 112.02, load_avg is 0.57.
+ After 52 seconds, recent_cpu is 114.89, load_avg is 0.58.
+ After 54 seconds, recent_cpu is 117.66, load_avg is 0.60.
+ After 56 seconds, recent_cpu is 120.34, load_avg is 0.61.
+ After 58 seconds, recent_cpu is 122.94, load_avg is 0.62.
+ After 60 seconds, recent_cpu is 125.46, load_avg is 0.64.
+ After 62 seconds, recent_cpu is 127.89, load_avg is 0.65.
+ After 64 seconds, recent_cpu is 130.25, load_avg is 0.66.
+ After 66 seconds, recent_cpu is 132.53, load_avg is 0.67.
+ After 68 seconds, recent_cpu is 134.73, load_avg is 0.68.
+ After 70 seconds, recent_cpu is 136.86, load_avg is 0.69.
+ After 72 seconds, recent_cpu is 138.93, load_avg is 0.70.
+ After 74 seconds, recent_cpu is 140.93, load_avg is 0.71.
+ After 76 seconds, recent_cpu is 142.86, load_avg is 0.72.
+ After 78 seconds, recent_cpu is 144.73, load_avg is 0.73.
+ After 80 seconds, recent_cpu is 146.54, load_avg is 0.74.
+ After 82 seconds, recent_cpu is 148.29, load_avg is 0.75.
+ After 84 seconds, recent_cpu is 149.99, load_avg is 0.76.
+ After 86 seconds, recent_cpu is 151.63, load_avg is 0.76.
+ After 88 seconds, recent_cpu is 153.21, load_avg is 0.77.
+ After 90 seconds, recent_cpu is 154.75, load_avg is 0.78.
+ After 92 seconds, recent_cpu is 156.23, load_avg is 0.79.
+ After 94 seconds, recent_cpu is 157.67, load_avg is 0.79.
+ After 96 seconds, recent_cpu is 159.06, load_avg is 0.80.
+ After 98 seconds, recent_cpu is 160.40, load_avg is 0.81.
+ After 100 seconds, recent_cpu is 161.70, load_avg is 0.81.
+ After 102 seconds, recent_cpu is 162.96, load_avg is 0.82.
+ After 104 seconds, recent_cpu is 164.18, load_avg is 0.83.
+ After 106 seconds, recent_cpu is 165.35, load_avg is 0.83.
+ After 108 seconds, recent_cpu is 166.49, load_avg is 0.84.
+ After 110 seconds, recent_cpu is 167.59, load_avg is 0.84.
+ After 112 seconds, recent_cpu is 168.66, load_avg is 0.85.
+ After 114 seconds, recent_cpu is 169.69, load_avg is 0.85.
+ After 116 seconds, recent_cpu is 170.69, load_avg is 0.86.
+ After 118 seconds, recent_cpu is 171.65, load_avg is 0.86.
+ After 120 seconds, recent_cpu is 172.58, load_avg is 0.87.
+ After 122 seconds, recent_cpu is 173.49, load_avg is 0.87.
+ After 124 seconds, recent_cpu is 174.36, load_avg is 0.88.
+ After 126 seconds, recent_cpu is 175.20, load_avg is 0.88.
+ After 128 seconds, recent_cpu is 176.02, load_avg is 0.88.
+ After 130 seconds, recent_cpu is 176.81, load_avg is 0.89.
+ After 132 seconds, recent_cpu is 177.57, load_avg is 0.89.
+ After 134 seconds, recent_cpu is 178.31, load_avg is 0.89.
+ After 136 seconds, recent_cpu is 179.02, load_avg is 0.90.
+ After 138 seconds, recent_cpu is 179.72, load_avg is 0.90.
+ After 140 seconds, recent_cpu is 180.38, load_avg is 0.90.
+ After 142 seconds, recent_cpu is 181.03, load_avg is 0.91.
+ After 144 seconds, recent_cpu is 181.65, load_avg is 0.91.
+ After 146 seconds, recent_cpu is 182.26, load_avg is 0.91.
+ After 148 seconds, recent_cpu is 182.84, load_avg is 0.92.
+ After 150 seconds, recent_cpu is 183.41, load_avg is 0.92.
+ After 152 seconds, recent_cpu is 183.96, load_avg is 0.92.
+ After 154 seconds, recent_cpu is 184.49, load_avg is 0.92.
+ After 156 seconds, recent_cpu is 185.00, load_avg is 0.93.
+ After 158 seconds, recent_cpu is 185.49, load_avg is 0.93.
+ After 160 seconds, recent_cpu is 185.97, load_avg is 0.93.
+ After 162 seconds, recent_cpu is 186.43, load_avg is 0.93.
+ After 164 seconds, recent_cpu is 186.88, load_avg is 0.94.
+ After 166 seconds, recent_cpu is 187.31, load_avg is 0.94.
+ After 168 seconds, recent_cpu is 187.73, load_avg is 0.94.
+ After 170 seconds, recent_cpu is 188.14, load_avg is 0.94.
+ After 172 seconds, recent_cpu is 188.53, load_avg is 0.94.
+ After 174 seconds, recent_cpu is 188.91, load_avg is 0.95.
+ After 176 seconds, recent_cpu is 189.27, load_avg is 0.95.
+ After 178 seconds, recent_cpu is 189.63, load_avg is 0.95.
+ After 180 seconds, recent_cpu is 189.97, load_avg is 0.95.
+*/
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "devices/timer.h"
+
+/* Sensitive to assumption that recent_cpu updates happen exactly
+ when timer_ticks() % TIMER_FREQ == 0. */
+
+void
+test_mlfqs_recent_1 (void)
+{
+ int64_t start_time;
+ int last_elapsed = 0;
+
+ ASSERT (thread_mlfqs);
+
+ do
+ {
+ msg ("Sleeping 10 seconds to allow recent_cpu to decay, please wait...");
+ start_time = timer_ticks ();
+ timer_sleep (DIV_ROUND_UP (start_time, TIMER_FREQ) - start_time
+ + 10 * TIMER_FREQ);
+ }
+ while (thread_get_recent_cpu () > 700);
+
+ start_time = timer_ticks ();
+ for (;;)
+ {
+ int elapsed = timer_elapsed (start_time);
+ if (elapsed % (TIMER_FREQ * 2) == 0 && elapsed > last_elapsed)
+ {
+ int recent_cpu = thread_get_recent_cpu ();
+ int load_avg = thread_get_load_avg ();
+ int elapsed_seconds = elapsed / TIMER_FREQ;
+ msg ("After %d seconds, recent_cpu is %d.%02d, load_avg is %d.%02d.",
+ elapsed_seconds,
+ recent_cpu / 100, recent_cpu % 100,
+ load_avg / 100, load_avg % 100);
+ if (elapsed_seconds >= 180)
+ break;
+ }
+ last_elapsed = elapsed;
+ }
+}
diff --git a/src/tests/threads/mlfqs-recent-1.ck b/src/tests/threads/mlfqs-recent-1.ck
new file mode 100644
index 0000000..a2ba44d
--- /dev/null
+++ b/src/tests/threads/mlfqs-recent-1.ck
@@ -0,0 +1,31 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::threads::mlfqs;
+
+our ($test);
+my (@output) = read_text_file ("$test.output");
+common_checks ("run", @output);
+@output = get_core_output ("run", @output);
+
+# Get actual values.
+local ($_);
+my (@actual);
+foreach (@output) {
+ my ($t, $recent_cpu) = /After (\d+) seconds, recent_cpu is (\d+\.\d+),/
+ or next;
+ $actual[$t] = $recent_cpu;
+}
+
+# Calculate expected values.
+my ($expected_load_avg, $expected_recent_cpu)
+ = mlfqs_expected_load ([(1) x 180], [(100) x 180]);
+my (@expected) = @$expected_recent_cpu;
+
+# Compare actual and expected values.
+mlfqs_compare ("time", "%.2f", \@actual, \@expected, 2.5, [2, 178, 2],
+ "Some recent_cpu values were missing or "
+ . "differed from those expected "
+ . "by more than 2.5.");
+pass;
diff --git a/src/tests/threads/mlfqs.pm b/src/tests/threads/mlfqs.pm
new file mode 100644
index 0000000..184ac16
--- /dev/null
+++ b/src/tests/threads/mlfqs.pm
@@ -0,0 +1,146 @@
+# -*- perl -*-
+use strict;
+use warnings;
+
+sub mlfqs_expected_load {
+ my ($ready, $recent_delta) = @_;
+ my (@load_avg) = 0;
+ my (@recent_cpu) = 0;
+ my ($load_avg) = 0;
+ my ($recent_cpu) = 0;
+ for my $i (0...$#$ready) {
+ $load_avg = (59/60) * $load_avg + (1/60) * $ready->[$i];
+ push (@load_avg, $load_avg);
+
+ if (defined $recent_delta->[$i]) {
+ my ($twice_load) = $load_avg * 2;
+ my ($load_factor) = $twice_load / ($twice_load + 1);
+ $recent_cpu = ($recent_cpu + $recent_delta->[$i]) * $load_factor;
+ push (@recent_cpu, $recent_cpu);
+ }
+ }
+ return (\@load_avg, \@recent_cpu);
+}
+
+sub mlfqs_expected_ticks {
+ my (@nice) = @_;
+ my ($thread_cnt) = scalar (@nice);
+ my (@recent_cpu) = (0) x $thread_cnt;
+ my (@slices) = (0) x $thread_cnt;
+ my (@fifo) = (0) x $thread_cnt;
+ my ($next_fifo) = 1;
+ my ($load_avg) = 0;
+ for my $i (1...750) {
+ if ($i % 25 == 0) {
+ # Update load average.
+ $load_avg = (59/60) * $load_avg + (1/60) * $thread_cnt;
+
+ # Update recent_cpu.
+ my ($twice_load) = $load_avg * 2;
+ my ($load_factor) = $twice_load / ($twice_load + 1);
+ $recent_cpu[$_] = $recent_cpu[$_] * $load_factor + $nice[$_]
+ foreach 0...($thread_cnt - 1);
+ }
+
+ # Update priorities.
+ my (@priority);
+ foreach my $j (0...($thread_cnt - 1)) {
+ my ($priority) = int ($recent_cpu[$j] / 4 + $nice[$j] * 2);
+ $priority = 0 if $priority < 0;
+ $priority = 63 if $priority > 63;
+ push (@priority, $priority);
+ }
+
+ # Choose thread to run.
+ my $max = 0;
+ for my $j (1...$#priority) {
+ if ($priority[$j] < $priority[$max]
+ || ($priority[$j] == $priority[$max]
+ && $fifo[$j] < $fifo[$max])) {
+ $max = $j;
+ }
+ }
+ $fifo[$max] = $next_fifo++;
+
+ # Run thread.
+ $recent_cpu[$max] += 4;
+ $slices[$max] += 4;
+ }
+ return @slices;
+}
+
+sub check_mlfqs_fair {
+ my ($nice, $maxdiff) = @_;
+ our ($test);
+ my (@output) = read_text_file ("$test.output");
+ common_checks ("run", @output);
+ @output = get_core_output ("run", @output);
+
+ my (@actual);
+ local ($_);
+ foreach (@output) {
+ my ($id, $count) = /Thread (\d+) received (\d+) ticks\./ or next;
+ $actual[$id] = $count;
+ }
+
+ my (@expected) = mlfqs_expected_ticks (@$nice);
+ mlfqs_compare ("thread", "%d",
+ \@actual, \@expected, $maxdiff, [0, $#$nice, 1],
+ "Some tick counts were missing or differed from those "
+ . "expected by more than $maxdiff.");
+ pass;
+}
+
+sub mlfqs_compare {
+ my ($indep_var, $format,
+ $actual_ref, $expected_ref, $maxdiff, $t_range, $message) = @_;
+ my ($t_min, $t_max, $t_step) = @$t_range;
+
+ my ($ok) = 1;
+ for (my ($t) = $t_min; $t <= $t_max; $t += $t_step) {
+ my ($actual) = $actual_ref->[$t];
+ my ($expected) = $expected_ref->[$t];
+ $ok = 0, last
+ if !defined ($actual) || abs ($actual - $expected) > $maxdiff + .01;
+ }
+ return if $ok;
+
+ print "$message\n";
+ mlfqs_row ($indep_var, "actual", "<->", "expected", "explanation");
+ mlfqs_row ("------", "--------", "---", "--------", '-' x 40);
+ for (my ($t) = $t_min; $t <= $t_max; $t += $t_step) {
+ my ($actual) = $actual_ref->[$t];
+ my ($expected) = $expected_ref->[$t];
+ my ($diff, $rationale);
+ if (!defined $actual) {
+ $actual = 'undef' ;
+ $diff = '';
+ $rationale = 'Missing value.';
+ } else {
+ my ($delta) = abs ($actual - $expected);
+ if ($delta > $maxdiff + .01) {
+ my ($excess) = $delta - $maxdiff;
+ if ($actual > $expected) {
+ $diff = '>>>';
+ $rationale = sprintf "Too big, by $format.", $excess;
+ } else {
+ $diff = '<<<';
+ $rationale = sprintf "Too small, by $format.", $excess;
+ }
+ } else {
+ $diff = ' = ';
+ $rationale = '';
+ }
+ $actual = sprintf ($format, $actual);
+ }
+ $expected = sprintf ($format, $expected);
+ mlfqs_row ($t, $actual, $diff, $expected, $rationale);
+ }
+ fail;
+}
+
+sub mlfqs_row {
+ printf "%6s %8s %3s %-8s %s\n", @_;
+}
+
+1;
diff --git a/src/tests/threads/priority-change.c b/src/tests/threads/priority-change.c
new file mode 100644
index 0000000..810b05a
--- /dev/null
+++ b/src/tests/threads/priority-change.c
@@ -0,0 +1,31 @@
+/* Verifies that lowering a thread's priority so that it is no
+ longer the highest-priority thread in the system causes it to
+ yield immediately. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/thread.h"
+
+static thread_func changing_thread;
+
+void
+test_priority_change (void)
+{
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ msg ("Creating a high-priority thread 2.");
+ thread_create ("thread 2", PRI_DEFAULT + 1, changing_thread, NULL);
+ msg ("Thread 2 should have just lowered its priority.");
+ thread_set_priority (PRI_DEFAULT - 2);
+ msg ("Thread 2 should have just exited.");
+}
+
+static void
+changing_thread (void *aux UNUSED)
+{
+ msg ("Thread 2 now lowering priority.");
+ thread_set_priority (PRI_DEFAULT - 1);
+ msg ("Thread 2 exiting.");
+}
diff --git a/src/tests/threads/priority-change.ck b/src/tests/threads/priority-change.ck
new file mode 100644
index 0000000..f4d9b2f
--- /dev/null
+++ b/src/tests/threads/priority-change.ck
@@ -0,0 +1,14 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(priority-change) begin
+(priority-change) Creating a high-priority thread 2.
+(priority-change) Thread 2 now lowering priority.
+(priority-change) Thread 2 should have just lowered its priority.
+(priority-change) Thread 2 exiting.
+(priority-change) Thread 2 should have just exited.
+(priority-change) end
+EOF
+pass;
diff --git a/src/tests/threads/priority-condvar.c b/src/tests/threads/priority-condvar.c
new file mode 100644
index 0000000..c1efb1b
--- /dev/null
+++ b/src/tests/threads/priority-condvar.c
@@ -0,0 +1,53 @@
+/* Tests that cond_signal() wakes up the highest-priority thread
+ waiting in cond_wait(). */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "devices/timer.h"
+
+static thread_func priority_condvar_thread;
+static struct lock lock;
+static struct condition condition;
+
+void
+test_priority_condvar (void)
+{
+ int i;
+
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ lock_init (&lock);
+ cond_init (&condition);
+
+ thread_set_priority (PRI_MIN);
+ for (i = 0; i < 10; i++)
+ {
+ int priority = PRI_DEFAULT - (i + 7) % 10 - 1;
+ char name[16];
+ snprintf (name, sizeof name, "priority %d", priority);
+ thread_create (name, priority, priority_condvar_thread, NULL);
+ }
+
+ for (i = 0; i < 10; i++)
+ {
+ lock_acquire (&lock);
+ msg ("Signaling...");
+ cond_signal (&condition, &lock);
+ lock_release (&lock);
+ }
+}
+
+static void
+priority_condvar_thread (void *aux UNUSED)
+{
+ msg ("Thread %s starting.", thread_name ());
+ lock_acquire (&lock);
+ cond_wait (&condition, &lock);
+ msg ("Thread %s woke up.", thread_name ());
+ lock_release (&lock);
+}
diff --git a/src/tests/threads/priority-condvar.ck b/src/tests/threads/priority-condvar.ck
new file mode 100644
index 0000000..195c1ab
--- /dev/null
+++ b/src/tests/threads/priority-condvar.ck
@@ -0,0 +1,39 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(priority-condvar) begin
+(priority-condvar) Thread priority 23 starting.
+(priority-condvar) Thread priority 22 starting.
+(priority-condvar) Thread priority 21 starting.
+(priority-condvar) Thread priority 30 starting.
+(priority-condvar) Thread priority 29 starting.
+(priority-condvar) Thread priority 28 starting.
+(priority-condvar) Thread priority 27 starting.
+(priority-condvar) Thread priority 26 starting.
+(priority-condvar) Thread priority 25 starting.
+(priority-condvar) Thread priority 24 starting.
+(priority-condvar) Signaling...
+(priority-condvar) Thread priority 30 woke up.
+(priority-condvar) Signaling...
+(priority-condvar) Thread priority 29 woke up.
+(priority-condvar) Signaling...
+(priority-condvar) Thread priority 28 woke up.
+(priority-condvar) Signaling...
+(priority-condvar) Thread priority 27 woke up.
+(priority-condvar) Signaling...
+(priority-condvar) Thread priority 26 woke up.
+(priority-condvar) Signaling...
+(priority-condvar) Thread priority 25 woke up.
+(priority-condvar) Signaling...
+(priority-condvar) Thread priority 24 woke up.
+(priority-condvar) Signaling...
+(priority-condvar) Thread priority 23 woke up.
+(priority-condvar) Signaling...
+(priority-condvar) Thread priority 22 woke up.
+(priority-condvar) Signaling...
+(priority-condvar) Thread priority 21 woke up.
+(priority-condvar) end
+EOF
+pass;
diff --git a/src/tests/threads/priority-donate-chain.c b/src/tests/threads/priority-donate-chain.c
new file mode 100644
index 0000000..3ffabca
--- /dev/null
+++ b/src/tests/threads/priority-donate-chain.c
@@ -0,0 +1,114 @@
+/* The main thread set its priority to PRI_MIN and creates 7 threads
+ (thread 1..7) with priorities PRI_MIN + 3, 6, 9, 12, ...
+ The main thread initializes 8 locks: lock 0..7 and acquires lock 0.
+
+ When thread[i] starts, it first acquires lock[i] (unless i == 7.)
+ Subsequently, thread[i] attempts to acquire lock[i-1], which is held by
+ thread[i-1], except for lock[0], which is held by the main thread.
+ Because the lock is held, thread[i] donates its priority to thread[i-1],
+ which donates to thread[i-2], and so on until the main thread
+ receives the donation.
+
+ After threads[1..7] have been created and are blocked on locks[0..7],
+ the main thread releases lock[0], unblocking thread[1], and being
+ preempted by it.
+ Thread[1] then completes acquiring lock[0], then releases lock[0],
+ then releases lock[1], unblocking thread[2], etc.
+ Thread[7] finally acquires & releases lock[7] and exits, allowing
+ thread[6], then thread[5] etc. to run and exit until finally the
+ main thread exits.
+
+ In addition, interloper threads are created at priority levels
+ p = PRI_MIN + 2, 5, 8, 11, ... which should not be run until the
+ corresponding thread with priority p + 1 has finished.
+
+ Written by Godmar Back <gback@cs.vt.edu> */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+#define NESTING_DEPTH 8
+
+struct lock_pair
+ {
+ struct lock *second;
+ struct lock *first;
+ };
+
+static thread_func donor_thread_func;
+static thread_func interloper_thread_func;
+
+void
+test_priority_donate_chain (void)
+{
+ int i;
+ struct lock locks[NESTING_DEPTH - 1];
+ struct lock_pair lock_pairs[NESTING_DEPTH];
+
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ thread_set_priority (PRI_MIN);
+
+ for (i = 0; i < NESTING_DEPTH - 1; i++)
+ lock_init (&locks[i]);
+
+ lock_acquire (&locks[0]);
+ msg ("%s got lock.", thread_name ());
+
+ for (i = 1; i < NESTING_DEPTH; i++)
+ {
+ char name[16];
+ int thread_priority;
+
+ snprintf (name, sizeof name, "thread %d", i);
+ thread_priority = PRI_MIN + i * 3;
+ lock_pairs[i].first = i < NESTING_DEPTH - 1 ? locks + i: NULL;
+ lock_pairs[i].second = locks + i - 1;
+
+ thread_create (name, thread_priority, donor_thread_func, lock_pairs + i);
+ msg ("%s should have priority %d. Actual priority: %d.",
+ thread_name (), thread_priority, thread_get_priority ());
+
+ snprintf (name, sizeof name, "interloper %d", i);
+ thread_create (name, thread_priority - 1, interloper_thread_func, NULL);
+ }
+
+ lock_release (&locks[0]);
+ msg ("%s finishing with priority %d.", thread_name (),
+ thread_get_priority ());
+}
+
+static void
+donor_thread_func (void *locks_)
+{
+ struct lock_pair *locks = locks_;
+
+ if (locks->first)
+ lock_acquire (locks->first);
+
+ lock_acquire (locks->second);
+ msg ("%s got lock", thread_name ());
+
+ lock_release (locks->second);
+ msg ("%s should have priority %d. Actual priority: %d",
+ thread_name (), (NESTING_DEPTH - 1) * 3,
+ thread_get_priority ());
+
+ if (locks->first)
+ lock_release (locks->first);
+
+ msg ("%s finishing with priority %d.", thread_name (),
+ thread_get_priority ());
+}
+
+static void
+interloper_thread_func (void *arg_ UNUSED)
+{
+ msg ("%s finished.", thread_name ());
+}
+
+// vim: sw=2
diff --git a/src/tests/threads/priority-donate-chain.ck b/src/tests/threads/priority-donate-chain.ck
new file mode 100644
index 0000000..213e649
--- /dev/null
+++ b/src/tests/threads/priority-donate-chain.ck
@@ -0,0 +1,46 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(priority-donate-chain) begin
+(priority-donate-chain) main got lock.
+(priority-donate-chain) main should have priority 3. Actual priority: 3.
+(priority-donate-chain) main should have priority 6. Actual priority: 6.
+(priority-donate-chain) main should have priority 9. Actual priority: 9.
+(priority-donate-chain) main should have priority 12. Actual priority: 12.
+(priority-donate-chain) main should have priority 15. Actual priority: 15.
+(priority-donate-chain) main should have priority 18. Actual priority: 18.
+(priority-donate-chain) main should have priority 21. Actual priority: 21.
+(priority-donate-chain) thread 1 got lock
+(priority-donate-chain) thread 1 should have priority 21. Actual priority: 21
+(priority-donate-chain) thread 2 got lock
+(priority-donate-chain) thread 2 should have priority 21. Actual priority: 21
+(priority-donate-chain) thread 3 got lock
+(priority-donate-chain) thread 3 should have priority 21. Actual priority: 21
+(priority-donate-chain) thread 4 got lock
+(priority-donate-chain) thread 4 should have priority 21. Actual priority: 21
+(priority-donate-chain) thread 5 got lock
+(priority-donate-chain) thread 5 should have priority 21. Actual priority: 21
+(priority-donate-chain) thread 6 got lock
+(priority-donate-chain) thread 6 should have priority 21. Actual priority: 21
+(priority-donate-chain) thread 7 got lock
+(priority-donate-chain) thread 7 should have priority 21. Actual priority: 21
+(priority-donate-chain) thread 7 finishing with priority 21.
+(priority-donate-chain) interloper 7 finished.
+(priority-donate-chain) thread 6 finishing with priority 18.
+(priority-donate-chain) interloper 6 finished.
+(priority-donate-chain) thread 5 finishing with priority 15.
+(priority-donate-chain) interloper 5 finished.
+(priority-donate-chain) thread 4 finishing with priority 12.
+(priority-donate-chain) interloper 4 finished.
+(priority-donate-chain) thread 3 finishing with priority 9.
+(priority-donate-chain) interloper 3 finished.
+(priority-donate-chain) thread 2 finishing with priority 6.
+(priority-donate-chain) interloper 2 finished.
+(priority-donate-chain) thread 1 finishing with priority 3.
+(priority-donate-chain) interloper 1 finished.
+(priority-donate-chain) main finishing with priority 0.
+(priority-donate-chain) end
+EOF
+pass;
diff --git a/src/tests/threads/priority-donate-lower.c b/src/tests/threads/priority-donate-lower.c
new file mode 100644
index 0000000..4965d75
--- /dev/null
+++ b/src/tests/threads/priority-donate-lower.c
@@ -0,0 +1,51 @@
+/* The main thread acquires a lock. Then it creates a
+ higher-priority thread that blocks acquiring the lock, causing
+ it to donate their priorities to the main thread. The main
+ thread attempts to lower its priority, which should not take
+ effect until the donation is released. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+static thread_func acquire_thread_func;
+
+void
+test_priority_donate_lower (void)
+{
+ struct lock lock;
+
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ /* Make sure our priority is the default. */
+ ASSERT (thread_get_priority () == PRI_DEFAULT);
+
+ lock_init (&lock);
+ lock_acquire (&lock);
+ thread_create ("acquire", PRI_DEFAULT + 10, acquire_thread_func, &lock);
+ msg ("Main thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT + 10, thread_get_priority ());
+
+ msg ("Lowering base priority...");
+ thread_set_priority (PRI_DEFAULT - 10);
+ msg ("Main thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT + 10, thread_get_priority ());
+ lock_release (&lock);
+ msg ("acquire must already have finished.");
+ msg ("Main thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT - 10, thread_get_priority ());
+}
+
+static void
+acquire_thread_func (void *lock_)
+{
+ struct lock *lock = lock_;
+
+ lock_acquire (lock);
+ msg ("acquire: got the lock");
+ lock_release (lock);
+ msg ("acquire: done");
+}
diff --git a/src/tests/threads/priority-donate-lower.ck b/src/tests/threads/priority-donate-lower.ck
new file mode 100644
index 0000000..c9bb61b
--- /dev/null
+++ b/src/tests/threads/priority-donate-lower.ck
@@ -0,0 +1,16 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(priority-donate-lower) begin
+(priority-donate-lower) Main thread should have priority 41. Actual priority: 41.
+(priority-donate-lower) Lowering base priority...
+(priority-donate-lower) Main thread should have priority 41. Actual priority: 41.
+(priority-donate-lower) acquire: got the lock
+(priority-donate-lower) acquire: done
+(priority-donate-lower) acquire must already have finished.
+(priority-donate-lower) Main thread should have priority 21. Actual priority: 21.
+(priority-donate-lower) end
+EOF
+pass;
diff --git a/src/tests/threads/priority-donate-multiple.c b/src/tests/threads/priority-donate-multiple.c
new file mode 100644
index 0000000..df4689c
--- /dev/null
+++ b/src/tests/threads/priority-donate-multiple.c
@@ -0,0 +1,77 @@
+/* The main thread acquires locks A and B, then it creates two
+ higher-priority threads. Each of these threads blocks
+ acquiring one of the locks and thus donate their priority to
+ the main thread. The main thread releases the locks in turn
+ and relinquishes its donated priorities.
+
+ Based on a test originally submitted for Stanford's CS 140 in
+ winter 1999 by Matt Franklin <startled@leland.stanford.edu>,
+ Greg Hutchins <gmh@leland.stanford.edu>, Yu Ping Hu
+ <yph@cs.stanford.edu>. Modified by arens. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+static thread_func a_thread_func;
+static thread_func b_thread_func;
+
+void
+test_priority_donate_multiple (void)
+{
+ struct lock a, b;
+
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ /* Make sure our priority is the default. */
+ ASSERT (thread_get_priority () == PRI_DEFAULT);
+
+ lock_init (&a);
+ lock_init (&b);
+
+ lock_acquire (&a);
+ lock_acquire (&b);
+
+ thread_create ("a", PRI_DEFAULT + 1, a_thread_func, &a);
+ msg ("Main thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT + 1, thread_get_priority ());
+
+ thread_create ("b", PRI_DEFAULT + 2, b_thread_func, &b);
+ msg ("Main thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT + 2, thread_get_priority ());
+
+ lock_release (&b);
+ msg ("Thread b should have just finished.");
+ msg ("Main thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT + 1, thread_get_priority ());
+
+ lock_release (&a);
+ msg ("Thread a should have just finished.");
+ msg ("Main thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT, thread_get_priority ());
+}
+
+static void
+a_thread_func (void *lock_)
+{
+ struct lock *lock = lock_;
+
+ lock_acquire (lock);
+ msg ("Thread a acquired lock a.");
+ lock_release (lock);
+ msg ("Thread a finished.");
+}
+
+static void
+b_thread_func (void *lock_)
+{
+ struct lock *lock = lock_;
+
+ lock_acquire (lock);
+ msg ("Thread b acquired lock b.");
+ lock_release (lock);
+ msg ("Thread b finished.");
+}
diff --git a/src/tests/threads/priority-donate-multiple.ck b/src/tests/threads/priority-donate-multiple.ck
new file mode 100644
index 0000000..0afd20b
--- /dev/null
+++ b/src/tests/threads/priority-donate-multiple.ck
@@ -0,0 +1,19 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(priority-donate-multiple) begin
+(priority-donate-multiple) Main thread should have priority 32. Actual priority: 32.
+(priority-donate-multiple) Main thread should have priority 33. Actual priority: 33.
+(priority-donate-multiple) Thread b acquired lock b.
+(priority-donate-multiple) Thread b finished.
+(priority-donate-multiple) Thread b should have just finished.
+(priority-donate-multiple) Main thread should have priority 32. Actual priority: 32.
+(priority-donate-multiple) Thread a acquired lock a.
+(priority-donate-multiple) Thread a finished.
+(priority-donate-multiple) Thread a should have just finished.
+(priority-donate-multiple) Main thread should have priority 31. Actual priority: 31.
+(priority-donate-multiple) end
+EOF
+pass;
diff --git a/src/tests/threads/priority-donate-multiple2.c b/src/tests/threads/priority-donate-multiple2.c
new file mode 100644
index 0000000..7f65fef
--- /dev/null
+++ b/src/tests/threads/priority-donate-multiple2.c
@@ -0,0 +1,90 @@
+/* The main thread acquires locks A and B, then it creates three
+ higher-priority threads. The first two of these threads block
+ acquiring one of the locks and thus donate their priority to
+ the main thread. The main thread releases the locks in turn
+ and relinquishes its donated priorities, allowing the third thread
+ to run.
+
+ In this test, the main thread releases the locks in a different
+ order compared to priority-donate-multiple.c.
+
+ Written by Godmar Back <gback@cs.vt.edu>.
+ Based on a test originally submitted for Stanford's CS 140 in
+ winter 1999 by Matt Franklin <startled@leland.stanford.edu>,
+ Greg Hutchins <gmh@leland.stanford.edu>, Yu Ping Hu
+ <yph@cs.stanford.edu>. Modified by arens. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+static thread_func a_thread_func;
+static thread_func b_thread_func;
+static thread_func c_thread_func;
+
+void
+test_priority_donate_multiple2 (void)
+{
+ struct lock a, b;
+
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ /* Make sure our priority is the default. */
+ ASSERT (thread_get_priority () == PRI_DEFAULT);
+
+ lock_init (&a);
+ lock_init (&b);
+
+ lock_acquire (&a);
+ lock_acquire (&b);
+
+ thread_create ("a", PRI_DEFAULT + 3, a_thread_func, &a);
+ msg ("Main thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT + 3, thread_get_priority ());
+
+ thread_create ("c", PRI_DEFAULT + 1, c_thread_func, NULL);
+
+ thread_create ("b", PRI_DEFAULT + 5, b_thread_func, &b);
+ msg ("Main thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT + 5, thread_get_priority ());
+
+ lock_release (&a);
+ msg ("Main thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT + 5, thread_get_priority ());
+
+ lock_release (&b);
+ msg ("Threads b, a, c should have just finished, in that order.");
+ msg ("Main thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT, thread_get_priority ());
+}
+
+static void
+a_thread_func (void *lock_)
+{
+ struct lock *lock = lock_;
+
+ lock_acquire (lock);
+ msg ("Thread a acquired lock a.");
+ lock_release (lock);
+ msg ("Thread a finished.");
+}
+
+static void
+b_thread_func (void *lock_)
+{
+ struct lock *lock = lock_;
+
+ lock_acquire (lock);
+ msg ("Thread b acquired lock b.");
+ lock_release (lock);
+ msg ("Thread b finished.");
+}
+
+static void
+c_thread_func (void *a_ UNUSED)
+{
+ msg ("Thread c finished.");
+}
diff --git a/src/tests/threads/priority-donate-multiple2.ck b/src/tests/threads/priority-donate-multiple2.ck
new file mode 100644
index 0000000..b23533a
--- /dev/null
+++ b/src/tests/threads/priority-donate-multiple2.ck
@@ -0,0 +1,19 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(priority-donate-multiple2) begin
+(priority-donate-multiple2) Main thread should have priority 34. Actual priority: 34.
+(priority-donate-multiple2) Main thread should have priority 36. Actual priority: 36.
+(priority-donate-multiple2) Main thread should have priority 36. Actual priority: 36.
+(priority-donate-multiple2) Thread b acquired lock b.
+(priority-donate-multiple2) Thread b finished.
+(priority-donate-multiple2) Thread a acquired lock a.
+(priority-donate-multiple2) Thread a finished.
+(priority-donate-multiple2) Thread c finished.
+(priority-donate-multiple2) Threads b, a, c should have just finished, in that order.
+(priority-donate-multiple2) Main thread should have priority 31. Actual priority: 31.
+(priority-donate-multiple2) end
+EOF
+pass;
diff --git a/src/tests/threads/priority-donate-nest.c b/src/tests/threads/priority-donate-nest.c
new file mode 100644
index 0000000..3a3a9a5
--- /dev/null
+++ b/src/tests/threads/priority-donate-nest.c
@@ -0,0 +1,94 @@
+/* Low-priority main thread L acquires lock A. Medium-priority
+ thread M then acquires lock B then blocks on acquiring lock A.
+ High-priority thread H then blocks on acquiring lock B. Thus,
+ thread H donates its priority to M, which in turn donates it
+ to thread L.
+
+ Based on a test originally submitted for Stanford's CS 140 in
+ winter 1999 by Matt Franklin <startled@leland.stanford.edu>,
+ Greg Hutchins <gmh@leland.stanford.edu>, Yu Ping Hu
+ <yph@cs.stanford.edu>. Modified by arens. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+struct locks
+ {
+ struct lock *a;
+ struct lock *b;
+ };
+
+static thread_func medium_thread_func;
+static thread_func high_thread_func;
+
+void
+test_priority_donate_nest (void)
+{
+ struct lock a, b;
+ struct locks locks;
+
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ /* Make sure our priority is the default. */
+ ASSERT (thread_get_priority () == PRI_DEFAULT);
+
+ lock_init (&a);
+ lock_init (&b);
+
+ lock_acquire (&a);
+
+ locks.a = &a;
+ locks.b = &b;
+ thread_create ("medium", PRI_DEFAULT + 1, medium_thread_func, &locks);
+ thread_yield ();
+ msg ("Low thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT + 1, thread_get_priority ());
+
+ thread_create ("high", PRI_DEFAULT + 2, high_thread_func, &b);
+ thread_yield ();
+ msg ("Low thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT + 2, thread_get_priority ());
+
+ lock_release (&a);
+ thread_yield ();
+ msg ("Medium thread should just have finished.");
+ msg ("Low thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT, thread_get_priority ());
+}
+
+static void
+medium_thread_func (void *locks_)
+{
+ struct locks *locks = locks_;
+
+ lock_acquire (locks->b);
+ lock_acquire (locks->a);
+
+ msg ("Medium thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT + 2, thread_get_priority ());
+ msg ("Medium thread got the lock.");
+
+ lock_release (locks->a);
+ thread_yield ();
+
+ lock_release (locks->b);
+ thread_yield ();
+
+ msg ("High thread should have just finished.");
+ msg ("Middle thread finished.");
+}
+
+static void
+high_thread_func (void *lock_)
+{
+ struct lock *lock = lock_;
+
+ lock_acquire (lock);
+ msg ("High thread got the lock.");
+ lock_release (lock);
+ msg ("High thread finished.");
+}
diff --git a/src/tests/threads/priority-donate-nest.ck b/src/tests/threads/priority-donate-nest.ck
new file mode 100644
index 0000000..923460e
--- /dev/null
+++ b/src/tests/threads/priority-donate-nest.ck
@@ -0,0 +1,19 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(priority-donate-nest) begin
+(priority-donate-nest) Low thread should have priority 32. Actual priority: 32.
+(priority-donate-nest) Low thread should have priority 33. Actual priority: 33.
+(priority-donate-nest) Medium thread should have priority 33. Actual priority: 33.
+(priority-donate-nest) Medium thread got the lock.
+(priority-donate-nest) High thread got the lock.
+(priority-donate-nest) High thread finished.
+(priority-donate-nest) High thread should have just finished.
+(priority-donate-nest) Middle thread finished.
+(priority-donate-nest) Medium thread should just have finished.
+(priority-donate-nest) Low thread should have priority 31. Actual priority: 31.
+(priority-donate-nest) end
+EOF
+pass;
diff --git a/src/tests/threads/priority-donate-one.c b/src/tests/threads/priority-donate-one.c
new file mode 100644
index 0000000..3189f3a
--- /dev/null
+++ b/src/tests/threads/priority-donate-one.c
@@ -0,0 +1,65 @@
+/* The main thread acquires a lock. Then it creates two
+ higher-priority threads that block acquiring the lock, causing
+ them to donate their priorities to the main thread. When the
+ main thread releases the lock, the other threads should
+ acquire it in priority order.
+
+ Based on a test originally submitted for Stanford's CS 140 in
+ winter 1999 by Matt Franklin <startled@leland.stanford.edu>,
+ Greg Hutchins <gmh@leland.stanford.edu>, Yu Ping Hu
+ <yph@cs.stanford.edu>. Modified by arens. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+static thread_func acquire1_thread_func;
+static thread_func acquire2_thread_func;
+
+void
+test_priority_donate_one (void)
+{
+ struct lock lock;
+
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ /* Make sure our priority is the default. */
+ ASSERT (thread_get_priority () == PRI_DEFAULT);
+
+ lock_init (&lock);
+ lock_acquire (&lock);
+ thread_create ("acquire1", PRI_DEFAULT + 1, acquire1_thread_func, &lock);
+ msg ("This thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT + 1, thread_get_priority ());
+ thread_create ("acquire2", PRI_DEFAULT + 2, acquire2_thread_func, &lock);
+ msg ("This thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT + 2, thread_get_priority ());
+ lock_release (&lock);
+ msg ("acquire2, acquire1 must already have finished, in that order.");
+ msg ("This should be the last line before finishing this test.");
+}
+
+static void
+acquire1_thread_func (void *lock_)
+{
+ struct lock *lock = lock_;
+
+ lock_acquire (lock);
+ msg ("acquire1: got the lock");
+ lock_release (lock);
+ msg ("acquire1: done");
+}
+
+static void
+acquire2_thread_func (void *lock_)
+{
+ struct lock *lock = lock_;
+
+ lock_acquire (lock);
+ msg ("acquire2: got the lock");
+ lock_release (lock);
+ msg ("acquire2: done");
+}
diff --git a/src/tests/threads/priority-donate-one.ck b/src/tests/threads/priority-donate-one.ck
new file mode 100644
index 0000000..b7c8e6f
--- /dev/null
+++ b/src/tests/threads/priority-donate-one.ck
@@ -0,0 +1,17 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(priority-donate-one) begin
+(priority-donate-one) This thread should have priority 32. Actual priority: 32.
+(priority-donate-one) This thread should have priority 33. Actual priority: 33.
+(priority-donate-one) acquire2: got the lock
+(priority-donate-one) acquire2: done
+(priority-donate-one) acquire1: got the lock
+(priority-donate-one) acquire1: done
+(priority-donate-one) acquire2, acquire1 must already have finished, in that order.
+(priority-donate-one) This should be the last line before finishing this test.
+(priority-donate-one) end
+EOF
+pass;
diff --git a/src/tests/threads/priority-donate-sema.c b/src/tests/threads/priority-donate-sema.c
new file mode 100644
index 0000000..b33cb72
--- /dev/null
+++ b/src/tests/threads/priority-donate-sema.c
@@ -0,0 +1,82 @@
+/* Low priority thread L acquires a lock, then blocks downing a
+ semaphore. Medium priority thread M then blocks waiting on
+ the same semaphore. Next, high priority thread H attempts to
+ acquire the lock, donating its priority to L.
+
+ Next, the main thread ups the semaphore, waking up L. L
+ releases the lock, which wakes up H. H "up"s the semaphore,
+ waking up M. H terminates, then M, then L, and finally the
+ main thread.
+
+ Written by Godmar Back <gback@cs.vt.edu>. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+struct lock_and_sema
+ {
+ struct lock lock;
+ struct semaphore sema;
+ };
+
+static thread_func l_thread_func;
+static thread_func m_thread_func;
+static thread_func h_thread_func;
+
+void
+test_priority_donate_sema (void)
+{
+ struct lock_and_sema ls;
+
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ /* Make sure our priority is the default. */
+ ASSERT (thread_get_priority () == PRI_DEFAULT);
+
+ lock_init (&ls.lock);
+ sema_init (&ls.sema, 0);
+ thread_create ("low", PRI_DEFAULT + 1, l_thread_func, &ls);
+ thread_create ("med", PRI_DEFAULT + 3, m_thread_func, &ls);
+ thread_create ("high", PRI_DEFAULT + 5, h_thread_func, &ls);
+ sema_up (&ls.sema);
+ msg ("Main thread finished.");
+}
+
+static void
+l_thread_func (void *ls_)
+{
+ struct lock_and_sema *ls = ls_;
+
+ lock_acquire (&ls->lock);
+ msg ("Thread L acquired lock.");
+ sema_down (&ls->sema);
+ msg ("Thread L downed semaphore.");
+ lock_release (&ls->lock);
+ msg ("Thread L finished.");
+}
+
+static void
+m_thread_func (void *ls_)
+{
+ struct lock_and_sema *ls = ls_;
+
+ sema_down (&ls->sema);
+ msg ("Thread M finished.");
+}
+
+static void
+h_thread_func (void *ls_)
+{
+ struct lock_and_sema *ls = ls_;
+
+ lock_acquire (&ls->lock);
+ msg ("Thread H acquired lock.");
+
+ sema_up (&ls->sema);
+ lock_release (&ls->lock);
+ msg ("Thread H finished.");
+}
diff --git a/src/tests/threads/priority-donate-sema.ck b/src/tests/threads/priority-donate-sema.ck
new file mode 100644
index 0000000..92b8d07
--- /dev/null
+++ b/src/tests/threads/priority-donate-sema.ck
@@ -0,0 +1,16 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(priority-donate-sema) begin
+(priority-donate-sema) Thread L acquired lock.
+(priority-donate-sema) Thread L downed semaphore.
+(priority-donate-sema) Thread H acquired lock.
+(priority-donate-sema) Thread H finished.
+(priority-donate-sema) Thread M finished.
+(priority-donate-sema) Thread L finished.
+(priority-donate-sema) Main thread finished.
+(priority-donate-sema) end
+EOF
+pass;
diff --git a/src/tests/threads/priority-fifo.c b/src/tests/threads/priority-fifo.c
new file mode 100644
index 0000000..3af98a3
--- /dev/null
+++ b/src/tests/threads/priority-fifo.c
@@ -0,0 +1,99 @@
+/* Creates several threads all at the same priority and ensures
+ that they consistently run in the same round-robin order.
+
+ Based on a test originally submitted for Stanford's CS 140 in
+ winter 1999 by by Matt Franklin
+ <startled@leland.stanford.edu>, Greg Hutchins
+ <gmh@leland.stanford.edu>, Yu Ping Hu <yph@cs.stanford.edu>.
+ Modified by arens. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "devices/timer.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+struct simple_thread_data
+ {
+ int id; /* Sleeper ID. */
+ int iterations; /* Iterations so far. */
+ struct lock *lock; /* Lock on output. */
+ int **op; /* Output buffer position. */
+ };
+
+#define THREAD_CNT 16
+#define ITER_CNT 16
+
+static thread_func simple_thread_func;
+
+void
+test_priority_fifo (void)
+{
+ struct simple_thread_data data[THREAD_CNT];
+ struct lock lock;
+ int *output, *op;
+ int i, cnt;
+
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ /* Make sure our priority is the default. */
+ ASSERT (thread_get_priority () == PRI_DEFAULT);
+
+ msg ("%d threads will iterate %d times in the same order each time.",
+ THREAD_CNT, ITER_CNT);
+ msg ("If the order varies then there is a bug.");
+
+ output = op = malloc (sizeof *output * THREAD_CNT * ITER_CNT * 2);
+ ASSERT (output != NULL);
+ lock_init (&lock);
+
+ thread_set_priority (PRI_DEFAULT + 2);
+ for (i = 0; i < THREAD_CNT; i++)
+ {
+ char name[16];
+ struct simple_thread_data *d = data + i;
+ snprintf (name, sizeof name, "%d", i);
+ d->id = i;
+ d->iterations = 0;
+ d->lock = &lock;
+ d->op = &op;
+ thread_create (name, PRI_DEFAULT + 1, simple_thread_func, d);
+ }
+
+ thread_set_priority (PRI_DEFAULT);
+ /* All the other threads now run to termination here. */
+ ASSERT (lock.holder == NULL);
+
+ cnt = 0;
+ for (; output < op; output++)
+ {
+ struct simple_thread_data *d;
+
+ ASSERT (*output >= 0 && *output < THREAD_CNT);
+ d = data + *output;
+ if (cnt % THREAD_CNT == 0)
+ printf ("(priority-fifo) iteration:");
+ printf (" %d", d->id);
+ if (++cnt % THREAD_CNT == 0)
+ printf ("\n");
+ d->iterations++;
+ }
+}
+
+static void
+simple_thread_func (void *data_)
+{
+ struct simple_thread_data *data = data_;
+ int i;
+
+ for (i = 0; i < ITER_CNT; i++)
+ {
+ lock_acquire (data->lock);
+ *(*data->op)++ = data->id;
+ lock_release (data->lock);
+ thread_yield ();
+ }
+}
diff --git a/src/tests/threads/priority-fifo.ck b/src/tests/threads/priority-fifo.ck
new file mode 100644
index 0000000..11f1dd3
--- /dev/null
+++ b/src/tests/threads/priority-fifo.ck
@@ -0,0 +1,63 @@
+# -*- perl -*-
+
+# The expected output looks like this:
+#
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+#
+# A different permutation of 0...15 is acceptable, but every line must
+# be in the same order.
+
+use strict;
+use warnings;
+use tests::tests;
+
+our ($test);
+my (@output) = read_text_file ("$test.output");
+
+common_checks ("run", @output);
+
+my ($thread_cnt) = 16;
+my ($iter_cnt) = 16;
+my (@order);
+my (@t) = (-1) x $thread_cnt;
+
+my (@iterations) = grep (/iteration:/, @output);
+fail "No iterations found in output.\n" if !@iterations;
+
+my (@numbering) = $iterations[0] =~ /(\d+)/g;
+fail "First iteration does not list exactly $thread_cnt threads.\n"
+ if @numbering != $thread_cnt;
+
+my (@sorted_numbering) = sort { $a <=> $b } @numbering;
+for my $i (0...$#sorted_numbering) {
+ if ($sorted_numbering[$i] != $i) {
+ fail "First iteration does not list all threads "
+ . "0...$#sorted_numbering\n";
+ }
+}
+
+for my $i (1...$#iterations) {
+ if ($iterations[$i] ne $iterations[0]) {
+ fail "Iteration $i differs from iteration 0\n";
+ }
+}
+
+fail "$iter_cnt iterations expected but " . scalar (@iterations) . " found\n"
+ if $iter_cnt != @iterations;
+
+pass;
diff --git a/src/tests/threads/priority-preempt.c b/src/tests/threads/priority-preempt.c
new file mode 100644
index 0000000..3c3aacb
--- /dev/null
+++ b/src/tests/threads/priority-preempt.c
@@ -0,0 +1,41 @@
+/* Ensures that a high-priority thread really preempts.
+
+ Based on a test originally submitted for Stanford's CS 140 in
+ winter 1999 by by Matt Franklin
+ <startled@leland.stanford.edu>, Greg Hutchins
+ <gmh@leland.stanford.edu>, Yu Ping Hu <yph@cs.stanford.edu>.
+ Modified by arens. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+static thread_func simple_thread_func;
+
+void
+test_priority_preempt (void)
+{
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ /* Make sure our priority is the default. */
+ ASSERT (thread_get_priority () == PRI_DEFAULT);
+
+ thread_create ("high-priority", PRI_DEFAULT + 1, simple_thread_func, NULL);
+ msg ("The high-priority thread should have already completed.");
+}
+
+static void
+simple_thread_func (void *aux UNUSED)
+{
+ int i;
+
+ for (i = 0; i < 5; i++)
+ {
+ msg ("Thread %s iteration %d", thread_name (), i);
+ thread_yield ();
+ }
+ msg ("Thread %s done!", thread_name ());
+}
diff --git a/src/tests/threads/priority-preempt.ck b/src/tests/threads/priority-preempt.ck
new file mode 100644
index 0000000..43a26ee
--- /dev/null
+++ b/src/tests/threads/priority-preempt.ck
@@ -0,0 +1,16 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(priority-preempt) begin
+(priority-preempt) Thread high-priority iteration 0
+(priority-preempt) Thread high-priority iteration 1
+(priority-preempt) Thread high-priority iteration 2
+(priority-preempt) Thread high-priority iteration 3
+(priority-preempt) Thread high-priority iteration 4
+(priority-preempt) Thread high-priority done!
+(priority-preempt) The high-priority thread should have already completed.
+(priority-preempt) end
+EOF
+pass;
diff --git a/src/tests/threads/priority-sema.c b/src/tests/threads/priority-sema.c
new file mode 100644
index 0000000..2834a88
--- /dev/null
+++ b/src/tests/threads/priority-sema.c
@@ -0,0 +1,45 @@
+/* Tests that the highest-priority thread waiting on a semaphore
+ is the first to wake up. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "devices/timer.h"
+
+static thread_func priority_sema_thread;
+static struct semaphore sema;
+
+void
+test_priority_sema (void)
+{
+ int i;
+
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ sema_init (&sema, 0);
+ thread_set_priority (PRI_MIN);
+ for (i = 0; i < 10; i++)
+ {
+ int priority = PRI_DEFAULT - (i + 3) % 10 - 1;
+ char name[16];
+ snprintf (name, sizeof name, "priority %d", priority);
+ thread_create (name, priority, priority_sema_thread, NULL);
+ }
+
+ for (i = 0; i < 10; i++)
+ {
+ sema_up (&sema);
+ msg ("Back in main thread.");
+ }
+}
+
+static void
+priority_sema_thread (void *aux UNUSED)
+{
+ sema_down (&sema);
+ msg ("Thread %s woke up.", thread_name ());
+}
diff --git a/src/tests/threads/priority-sema.ck b/src/tests/threads/priority-sema.ck
new file mode 100644
index 0000000..559988d
--- /dev/null
+++ b/src/tests/threads/priority-sema.ck
@@ -0,0 +1,29 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(priority-sema) begin
+(priority-sema) Thread priority 30 woke up.
+(priority-sema) Back in main thread.
+(priority-sema) Thread priority 29 woke up.
+(priority-sema) Back in main thread.
+(priority-sema) Thread priority 28 woke up.
+(priority-sema) Back in main thread.
+(priority-sema) Thread priority 27 woke up.
+(priority-sema) Back in main thread.
+(priority-sema) Thread priority 26 woke up.
+(priority-sema) Back in main thread.
+(priority-sema) Thread priority 25 woke up.
+(priority-sema) Back in main thread.
+(priority-sema) Thread priority 24 woke up.
+(priority-sema) Back in main thread.
+(priority-sema) Thread priority 23 woke up.
+(priority-sema) Back in main thread.
+(priority-sema) Thread priority 22 woke up.
+(priority-sema) Back in main thread.
+(priority-sema) Thread priority 21 woke up.
+(priority-sema) Back in main thread.
+(priority-sema) end
+EOF
+pass;
diff --git a/src/tests/threads/simplethreadtest.c b/src/tests/threads/simplethreadtest.c
new file mode 100644
index 0000000..f9c058e
--- /dev/null
+++ b/src/tests/threads/simplethreadtest.c
@@ -0,0 +1,68 @@
+// threadtest.cc
+// Simple test case for the threads assignment.
+//
+// Create two threads, and have them context switch
+// back and forth between themselves by calling Thread::Yield,
+// to illustratethe inner workings of the thread system.
+//
+// Copyright (c) 1992-1993 The Regents of the University of California.
+// All rights reserved. See copyright.h for copyright notice and limitation
+// of liability and disclaimer of warranty provisions.
+//
+// Modified by Viacheslav Izosimov
+// - transition from C++ to C (from Nachos to Pintos)
+
+
+//#include "copyright.h"
+//#include "system.h"
+#include "threads/boundedbuffer.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "tests/threads/tests.h"
+#include <stdio.h>
+#include <string.h>
+
+//----------------------------------------------------------------------
+// SimpleThread
+// Loop 5 times, yielding the CPU to another ready thread
+// each iteration.
+//
+// "which" is simply a number identifying the thread, for debugging
+// purposes.
+//----------------------------------------------------------------------
+
+void SimpleThread(void *);
+
+void
+SimpleThread(void * which)
+{
+ int num;
+
+ for (num = 0; num < 5; num++) {
+ printf("*** thread %d looped %d times\n", (int)which, num);
+ thread_yield();
+// currentThread->Yield();
+ }
+}
+
+//----------------------------------------------------------------------
+// ThreadTest
+// Set up a ping-pong between two threads, by forking a thread
+// to call SimpleThread, and then calling SimpleThread ourselves.
+//----------------------------------------------------------------------
+
+void
+SimpleThreadTest(void)
+{
+// DEBUG('t', "Entering SimpleTest");
+
+// Thread *t = new Thread("forked thread");
+ char *t_name = "forked thread";
+ printf("Entering SimpleTest");
+
+ thread_create(t_name, PRI_MIN, SimpleThread, (void *)1);
+
+// t->Fork(SimpleThread, 1);
+ SimpleThread((void *)0);
+}
diff --git a/src/tests/threads/tests.c b/src/tests/threads/tests.c
new file mode 100644
index 0000000..b8d090d
--- /dev/null
+++ b/src/tests/threads/tests.c
@@ -0,0 +1,104 @@
+#include "tests/threads/tests.h"
+#include <debug.h>
+#include <string.h>
+#include <stdio.h>
+
+struct test
+ {
+ const char *name;
+ test_func *function;
+ };
+
+static const struct test tests[] =
+ {
+ {"alarm-single", test_alarm_single},
+ {"alarm-multiple", test_alarm_multiple},
+ {"alarm-simultaneous", test_alarm_simultaneous},
+ {"alarm-priority", test_alarm_priority},
+ {"alarm-zero", test_alarm_zero},
+ {"alarm-negative", test_alarm_negative},
+ {"priority-change", test_priority_change},
+ {"priority-donate-one", test_priority_donate_one},
+ {"priority-donate-multiple", test_priority_donate_multiple},
+ {"priority-donate-multiple2", test_priority_donate_multiple2},
+ {"priority-donate-nest", test_priority_donate_nest},
+ {"priority-donate-sema", test_priority_donate_sema},
+ {"priority-donate-lower", test_priority_donate_lower},
+ {"priority-donate-chain", test_priority_donate_chain},
+ {"priority-fifo", test_priority_fifo},
+ {"priority-preempt", test_priority_preempt},
+ {"priority-sema", test_priority_sema},
+ {"priority-condvar", test_priority_condvar},
+ {"mlfqs-load-1", test_mlfqs_load_1},
+ {"mlfqs-load-60", test_mlfqs_load_60},
+ {"mlfqs-load-avg", test_mlfqs_load_avg},
+ {"mlfqs-recent-1", test_mlfqs_recent_1},
+ {"mlfqs-fair-2", test_mlfqs_fair_2},
+ {"mlfqs-fair-20", test_mlfqs_fair_20},
+ {"mlfqs-nice-2", test_mlfqs_nice_2},
+ {"mlfqs-nice-10", test_mlfqs_nice_10},
+ {"mlfqs-block", test_mlfqs_block},
+ {"threadtest", ThreadTest},
+ {"simplethreadtest", SimpleThreadTest}
+ };
+
+static const char *test_name;
+
+/* Runs the test named NAME. */
+void
+run_test (const char *name)
+{
+ const struct test *t;
+
+ for (t = tests; t < tests + sizeof tests / sizeof *tests; t++)
+ if (!strcmp (name, t->name))
+ {
+ test_name = name;
+ msg ("begin");
+ t->function ();
+ msg ("end");
+ return;
+ }
+ PANIC ("no test named \"%s\"", name);
+}
+
+/* Prints FORMAT as if with printf(),
+ prefixing the output by the name of the test
+ and following it with a new-line character. */
+void
+msg (const char *format, ...)
+{
+ va_list args;
+
+ printf ("(%s) ", test_name);
+ va_start (args, format);
+ vprintf (format, args);
+ va_end (args);
+ putchar ('\n');
+}
+
+/* Prints failure message FORMAT as if with printf(),
+ prefixing the output by the name of the test and FAIL:
+ and following it with a new-line character,
+ and then panics the kernel. */
+void
+fail (const char *format, ...)
+{
+ va_list args;
+
+ printf ("(%s) FAIL: ", test_name);
+ va_start (args, format);
+ vprintf (format, args);
+ va_end (args);
+ putchar ('\n');
+
+ PANIC ("test failed");
+}
+
+/* Prints a message indicating the current test passed. */
+void
+pass (void)
+{
+ printf ("(%s) PASS\n", test_name);
+}
+
diff --git a/src/tests/threads/tests.h b/src/tests/threads/tests.h
new file mode 100644
index 0000000..1fe3582
--- /dev/null
+++ b/src/tests/threads/tests.h
@@ -0,0 +1,43 @@
+#ifndef TESTS_THREADS_TESTS_H
+#define TESTS_THREADS_TESTS_H
+
+void run_test (const char *);
+
+typedef void test_func (void);
+
+extern test_func test_alarm_single;
+extern test_func test_alarm_multiple;
+extern test_func test_alarm_simultaneous;
+extern test_func test_alarm_priority;
+extern test_func test_alarm_zero;
+extern test_func test_alarm_negative;
+extern test_func test_priority_change;
+extern test_func test_priority_donate_one;
+extern test_func test_priority_donate_multiple;
+extern test_func test_priority_donate_multiple2;
+extern test_func test_priority_donate_sema;
+extern test_func test_priority_donate_nest;
+extern test_func test_priority_donate_lower;
+extern test_func test_priority_donate_chain;
+extern test_func test_priority_fifo;
+extern test_func test_priority_preempt;
+extern test_func test_priority_sema;
+extern test_func test_priority_condvar;
+extern test_func test_mlfqs_load_1;
+extern test_func test_mlfqs_load_60;
+extern test_func test_mlfqs_load_avg;
+extern test_func test_mlfqs_recent_1;
+extern test_func test_mlfqs_fair_2;
+extern test_func test_mlfqs_fair_20;
+extern test_func test_mlfqs_nice_2;
+extern test_func test_mlfqs_nice_10;
+extern test_func test_mlfqs_block;
+extern test_func ThreadTest;
+extern test_func SimpleThreadTest;
+
+void msg (const char *, ...);
+void fail (const char *, ...);
+void pass (void);
+
+#endif /* tests/threads/tests.h */
+
diff --git a/src/tests/threads/threadtest.c b/src/tests/threads/threadtest.c
new file mode 100644
index 0000000..363d532
--- /dev/null
+++ b/src/tests/threads/threadtest.c
@@ -0,0 +1,250 @@
+// threadtest.c
+// Simple test case for the threads assignment.
+//
+// Create seven threads, and have them context switch
+// back and forth between themselves by calling Thread::Yield,
+// to illustrate the inner workings of the thread system.
+//
+// Copyright (c) 1992-1993 The Regents of the University of California.
+// All rights reserved. See copyright.h for copyright notice and limitation
+// of liability and disclaimer of warranty provisions.
+//
+// Modified by Levon Saldamli.
+// Modified by Andrzej Bednarski:
+// - added #ifdef (do not require modification of Makefile)
+// Modified by Vlad Jahundovics:
+// - transition from C++ to C (from Nachos to Pintos)
+
+//#ifdef THREADS
+
+//#include "copyright.h"
+//#include "system.h"
+//#include "synch.h"
+//#include "boundedbuffer.h"
+#include "threads/boundedbuffer.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "tests/threads/tests.h"
+#include <stdio.h>
+#include <string.h>
+
+#define NBR_OF_PROD 50
+//const int nbr_of_prod = 50;
+#define NBR_OF_CON 50
+//const int nbr_of_con = 50;
+const int prod_switch = 30;
+const int con_switch = 20;
+
+char * prod_name[NBR_OF_PROD];
+char * con_name[NBR_OF_CON];
+
+char * msg_array[NBR_OF_PROD];
+
+char * received_msg_array[NBR_OF_PROD];
+int received_msg_pos[NBR_OF_PROD];
+
+
+struct bounded_buffer bounded_buffer[2];
+
+struct lock readlock[2];
+
+struct lock start_lock;
+struct condition start_cond;
+
+int started_threads;
+
+/*
+class Data
+{
+public:
+ Data(char ch, char* s, int i) {
+ c = ch;
+ sender = s;
+ sender_index = i;
+ }
+ char c;
+ char *sender;
+ int sender_index;
+};
+*/
+
+struct Data
+{
+ char c;
+ char *sender;
+ int sender_index;
+};
+
+void data_init(struct Data *data, char ch, char* s, int i);
+void WaitForStart(void);
+void Producer(void *index);
+void Consumer(void *index);
+
+void data_init(struct Data *data, char ch, char* s, int i)
+{
+ data->c = ch;
+ data->sender = s;
+ data->sender_index = i;
+}
+
+void
+WaitForStart(void) {
+ lock_acquire(&start_lock);
+ printf("%s is waiting for start signal\n", thread_name());
+ cond_wait(&start_cond,&start_lock);
+ printf("%s is starting\n", thread_name());
+ started_threads++;
+ lock_release(&start_lock);
+}
+
+void
+Producer(void *index)
+{
+ char *sender_name;
+ size_t sender_name_length;
+ WaitForStart();
+ int buf=0;
+ if ((int) index >= prod_switch)
+ buf=1;
+
+ char *msg = msg_array[(int) index];
+ void *p;
+
+ for (; *msg != '\0'; ++msg) {
+ p = malloc(sizeof(struct Data));
+ sender_name_length = strlen(thread_name())+1;
+ sender_name = calloc(sizeof(char), sender_name_length);
+ strlcpy(sender_name,thread_name(), sender_name_length);
+ data_init(p, *msg, sender_name, (int) index);
+ bb_write(&bounded_buffer[buf],(int) p);
+ thread_yield();
+ }
+ p = malloc(sizeof(struct Data));
+ sender_name_length = strlen(thread_name())+1;
+ sender_name = calloc(sizeof(char), sender_name_length);
+ strlcpy(sender_name,thread_name(), sender_name_length);
+ data_init(p, 0, sender_name, (int) index);
+ bb_write(&bounded_buffer[buf],(int) p);
+ printf("%s has finished sending.\n", thread_name());
+}
+
+void
+Consumer(void *index)
+{
+ WaitForStart();
+
+ int buf=0;
+ if ((int) index >= con_switch)
+ buf=1;
+
+ while (true) {
+
+ lock_acquire(&readlock[buf]);
+ struct Data *data = (struct Data*) bb_read(&bounded_buffer[buf]);
+ int i=data->sender_index;
+ received_msg_array[i][received_msg_pos[i]] = data->c;
+ received_msg_pos[i]++;
+ lock_release(&readlock[buf]);
+
+ if (data->c != 0);
+ /* DEBUG('c', "%s received from %s: %c\n",
+ currentThread->getName(),
+ data->sender,
+ data->c);*/
+ else
+ printf("\n%s: %s's total message was: \n\"%s\"\n",
+ thread_name(),
+ data->sender,
+ received_msg_array[i]);
+ free(data->sender);
+ free(data);
+ thread_yield();
+ }
+}
+
+
+
+//----------------------------------------------------------------------
+// ThreadTest
+// Creates three consumers and three producers. The producers
+// write different messages to the buffer. The main thread also
+// calls Consumer, resulting in four consumers.
+//----------------------------------------------------------------------
+
+void
+ThreadTest(void)
+{
+ //DEBUG('t', "Entering SimpleTest\n");
+ const int nmsg = 5;
+ char *msg[nmsg];
+ msg[0] = "Computer, compute to the last digit the value of pi!";
+ msg[1] = "What is now proved was once only imagined.";
+ msg[2] = "Insufficient facts always invites danger, Captain.";
+ msg[3] = "The Federation's gone; the Borg is everywhere!";
+ msg[4] = "Live long and prosper, Spock.";
+
+ // bounded_buffer[0] = new BoundedBuffer(5);
+ // bounded_buffer[1] = new BoundedBuffer(5);
+
+ printf("ThreadTest has just started! It's thread name is %s.\n", thread_name());
+
+ bb_init(&bounded_buffer[0],5);
+ bb_init(&bounded_buffer[1],5);
+ lock_init(&readlock[0]);
+ lock_init(&readlock[1]);
+ // readlock[0] = new Lock("Read lock 0");
+ // readlock[1] = new Lock("Read lock 1");
+
+ lock_init(&start_lock);
+ // start_lock = new Lock("Start lock");
+ cond_init(&start_cond);
+ // start_cond = new Condition("Start cond");
+ started_threads = 0;
+
+ char pname[] = "Producer";
+ char cname[] = "Consumer";
+ int i;
+
+ for (i=0;i < NBR_OF_PROD; i++) {
+ // char *str = new char[strlen(pname)+4];
+ char *str = (char *) calloc(sizeof(char), strlen(pname)+4);
+ snprintf(str,strlen(pname)+4,"%s %02d", pname, i);
+
+ prod_name[i] = str;
+ msg_array[i] = msg[i%nmsg];
+ received_msg_array[i] = (char *) calloc(sizeof(char),strlen(msg_array[i])+1);
+ //received_msg_array[i] = new char[strlen(msg_array[i])+1];
+ received_msg_pos[i] = 0;
+ // printf("Creating thread with the name: %s\n",str);
+ thread_create(str, PRI_MIN, Producer, (void *) i);
+ free(str);
+ // Thread *t = new Thread(str);
+ // t->Fork(Producer, i);
+ }
+ for (i=0;i < NBR_OF_CON; i++) {
+ char *str = (char *) calloc(sizeof(char), strlen(cname)+4);
+ //char *str = new char[strlen(cname)+4];
+ snprintf(str, strlen(cname)+4,"%s %02d", cname, i);
+ // printf("Creating thread with the name: %s\n",str);
+ thread_create(str, PRI_MIN, Consumer, (void *) i);
+ free(str);
+ //Thread *t = new Thread(str);
+ //t->Fork(Consumer, i);
+ }
+
+ thread_yield();
+
+ lock_acquire(&start_lock);
+ while (started_threads < (NBR_OF_PROD + NBR_OF_CON) ) {
+ printf("\n\n%s : All threads haven't started. Broadcasting start signal to all threads\n\n", thread_name());
+ cond_broadcast(&start_cond, &start_lock);
+ // start_cond->Broadcast(start_lock);
+ lock_release(&start_lock);
+ thread_yield();
+ lock_acquire(&start_lock);
+ }
+ printf("\n\nAll threads have started. Finishing %s\n\n", thread_name());
+}
+
+//#endif // THREADS
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;
diff --git a/src/tests/vm/Grading b/src/tests/vm/Grading
new file mode 100644
index 0000000..f0c2c13
--- /dev/null
+++ b/src/tests/vm/Grading
@@ -0,0 +1,12 @@
+# Percentage of the testing point total designated for each set of
+# tests.
+
+# This project is primarily about virtual memory, but all the previous
+# functionality should work too, and it's easy to screw it up, thus
+# the equal weight placed on each.
+
+50% tests/vm/Rubric.functionality
+15% tests/vm/Rubric.robustness
+10% tests/userprog/Rubric.functionality
+5% tests/userprog/Rubric.robustness
+20% tests/filesys/base/Rubric
diff --git a/src/tests/vm/Make.tests b/src/tests/vm/Make.tests
new file mode 100644
index 0000000..04b1b81
--- /dev/null
+++ b/src/tests/vm/Make.tests
@@ -0,0 +1,103 @@
+# -*- makefile -*-
+
+tests/vm_TESTS = $(addprefix tests/vm/,pt-grow-stack pt-grow-pusha \
+pt-grow-bad pt-big-stk-obj pt-bad-addr pt-bad-read pt-write-code \
+pt-write-code2 pt-grow-stk-sc page-linear page-parallel page-merge-seq \
+page-merge-par page-merge-stk page-merge-mm page-shuffle mmap-read \
+mmap-close mmap-unmap mmap-overlap mmap-twice mmap-write mmap-exit \
+mmap-shuffle mmap-bad-fd mmap-clean mmap-inherit mmap-misalign \
+mmap-null mmap-over-code mmap-over-data mmap-over-stk mmap-remove \
+mmap-zero)
+
+tests/vm_PROGS = $(tests/vm_TESTS) $(addprefix tests/vm/,child-linear \
+child-sort child-qsort child-qsort-mm child-mm-wrt child-inherit)
+
+tests/vm/pt-grow-stack_SRC = tests/vm/pt-grow-stack.c tests/arc4.c \
+tests/cksum.c tests/lib.c tests/main.c
+tests/vm/pt-grow-pusha_SRC = tests/vm/pt-grow-pusha.c tests/lib.c \
+tests/main.c
+tests/vm/pt-grow-bad_SRC = tests/vm/pt-grow-bad.c tests/lib.c tests/main.c
+tests/vm/pt-big-stk-obj_SRC = tests/vm/pt-big-stk-obj.c tests/arc4.c \
+tests/cksum.c tests/lib.c tests/main.c
+tests/vm/pt-bad-addr_SRC = tests/vm/pt-bad-addr.c tests/lib.c tests/main.c
+tests/vm/pt-bad-read_SRC = tests/vm/pt-bad-read.c tests/lib.c tests/main.c
+tests/vm/pt-write-code_SRC = tests/vm/pt-write-code.c tests/lib.c tests/main.c
+tests/vm/pt-write-code2_SRC = tests/vm/pt-write-code-2.c tests/lib.c tests/main.c
+tests/vm/pt-grow-stk-sc_SRC = tests/vm/pt-grow-stk-sc.c tests/lib.c tests/main.c
+tests/vm/page-linear_SRC = tests/vm/page-linear.c tests/arc4.c \
+tests/lib.c tests/main.c
+tests/vm/page-parallel_SRC = tests/vm/page-parallel.c tests/lib.c tests/main.c
+tests/vm/page-merge-seq_SRC = tests/vm/page-merge-seq.c tests/arc4.c \
+tests/lib.c tests/main.c
+tests/vm/page-merge-par_SRC = tests/vm/page-merge-par.c \
+tests/vm/parallel-merge.c tests/arc4.c tests/lib.c tests/main.c
+tests/vm/page-merge-stk_SRC = tests/vm/page-merge-stk.c \
+tests/vm/parallel-merge.c tests/arc4.c tests/lib.c tests/main.c
+tests/vm/page-merge-mm_SRC = tests/vm/page-merge-mm.c \
+tests/vm/parallel-merge.c tests/arc4.c tests/lib.c tests/main.c
+tests/vm/page-shuffle_SRC = tests/vm/page-shuffle.c tests/arc4.c \
+tests/cksum.c tests/lib.c tests/main.c
+tests/vm/mmap-read_SRC = tests/vm/mmap-read.c tests/lib.c tests/main.c
+tests/vm/mmap-close_SRC = tests/vm/mmap-close.c tests/lib.c tests/main.c
+tests/vm/mmap-unmap_SRC = tests/vm/mmap-unmap.c tests/lib.c tests/main.c
+tests/vm/mmap-overlap_SRC = tests/vm/mmap-overlap.c tests/lib.c tests/main.c
+tests/vm/mmap-twice_SRC = tests/vm/mmap-twice.c tests/lib.c tests/main.c
+tests/vm/mmap-write_SRC = tests/vm/mmap-write.c tests/lib.c tests/main.c
+tests/vm/mmap-exit_SRC = tests/vm/mmap-exit.c tests/lib.c tests/main.c
+tests/vm/mmap-shuffle_SRC = tests/vm/mmap-shuffle.c tests/arc4.c \
+tests/cksum.c tests/lib.c tests/main.c
+tests/vm/mmap-bad-fd_SRC = tests/vm/mmap-bad-fd.c tests/lib.c tests/main.c
+tests/vm/mmap-clean_SRC = tests/vm/mmap-clean.c tests/lib.c tests/main.c
+tests/vm/mmap-inherit_SRC = tests/vm/mmap-inherit.c tests/lib.c tests/main.c
+tests/vm/mmap-misalign_SRC = tests/vm/mmap-misalign.c tests/lib.c \
+tests/main.c
+tests/vm/mmap-null_SRC = tests/vm/mmap-null.c tests/lib.c tests/main.c
+tests/vm/mmap-over-code_SRC = tests/vm/mmap-over-code.c tests/lib.c \
+tests/main.c
+tests/vm/mmap-over-data_SRC = tests/vm/mmap-over-data.c tests/lib.c \
+tests/main.c
+tests/vm/mmap-over-stk_SRC = tests/vm/mmap-over-stk.c tests/lib.c tests/main.c
+tests/vm/mmap-remove_SRC = tests/vm/mmap-remove.c tests/lib.c tests/main.c
+tests/vm/mmap-zero_SRC = tests/vm/mmap-zero.c tests/lib.c tests/main.c
+
+tests/vm/child-linear_SRC = tests/vm/child-linear.c tests/arc4.c tests/lib.c
+tests/vm/child-qsort_SRC = tests/vm/child-qsort.c tests/vm/qsort.c tests/lib.c
+tests/vm/child-qsort-mm_SRC = tests/vm/child-qsort-mm.c tests/vm/qsort.c \
+tests/lib.c
+tests/vm/child-sort_SRC = tests/vm/child-sort.c tests/lib.c
+tests/vm/child-mm-wrt_SRC = tests/vm/child-mm-wrt.c tests/lib.c tests/main.c
+tests/vm/child-inherit_SRC = tests/vm/child-inherit.c tests/lib.c tests/main.c
+
+tests/vm/pt-bad-read_PUTFILES = tests/vm/sample.txt
+tests/vm/pt-write-code2_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-close_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-read_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-unmap_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-twice_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-overlap_PUTFILES = tests/vm/zeros
+tests/vm/mmap-exit_PUTFILES = tests/vm/child-mm-wrt
+tests/vm/page-parallel_PUTFILES = tests/vm/child-linear
+tests/vm/page-merge-seq_PUTFILES = tests/vm/child-sort
+tests/vm/page-merge-par_PUTFILES = tests/vm/child-sort
+tests/vm/page-merge-stk_PUTFILES = tests/vm/child-qsort
+tests/vm/page-merge-mm_PUTFILES = tests/vm/child-qsort-mm
+tests/vm/mmap-clean_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-inherit_PUTFILES = tests/vm/sample.txt tests/vm/child-inherit
+tests/vm/mmap-misalign_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-null_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-over-code_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-over-data_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-over-stk_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-remove_PUTFILES = tests/vm/sample.txt
+
+tests/vm/page-linear.output: TIMEOUT = 300
+tests/vm/page-shuffle.output: TIMEOUT = 600
+tests/vm/mmap-shuffle.output: TIMEOUT = 600
+tests/vm/page-merge-seq.output: TIMEOUT = 600
+tests/vm/page-merge-par.output: TIMEOUT = 600
+
+tests/vm/zeros:
+ dd if=/dev/zero of=$@ bs=1024 count=6
+
+clean::
+ rm -f tests/vm/zeros
diff --git a/src/tests/vm/Rubric.functionality b/src/tests/vm/Rubric.functionality
new file mode 100644
index 0000000..8a86612
--- /dev/null
+++ b/src/tests/vm/Rubric.functionality
@@ -0,0 +1,30 @@
+Functionality of virtual memory subsystem:
+- Test stack growth.
+3 pt-grow-stack
+3 pt-grow-stk-sc
+3 pt-big-stk-obj
+3 pt-grow-pusha
+
+- Test paging behavior.
+3 page-linear
+3 page-parallel
+3 page-shuffle
+4 page-merge-seq
+4 page-merge-par
+4 page-merge-mm
+4 page-merge-stk
+
+- Test "mmap" system call.
+2 mmap-read
+2 mmap-write
+2 mmap-shuffle
+
+2 mmap-twice
+
+2 mmap-unmap
+1 mmap-exit
+
+3 mmap-clean
+
+2 mmap-close
+2 mmap-remove
diff --git a/src/tests/vm/Rubric.robustness b/src/tests/vm/Rubric.robustness
new file mode 100644
index 0000000..0b2552f
--- /dev/null
+++ b/src/tests/vm/Rubric.robustness
@@ -0,0 +1,21 @@
+Robustness of virtual memory subsystem:
+- Test robustness of page table support.
+2 pt-bad-addr
+3 pt-bad-read
+2 pt-write-code
+3 pt-write-code2
+4 pt-grow-bad
+
+- Test robustness of "mmap" system call.
+1 mmap-bad-fd
+1 mmap-inherit
+1 mmap-null
+1 mmap-zero
+
+2 mmap-misalign
+
+2 mmap-over-code
+2 mmap-over-data
+2 mmap-over-stk
+2 mmap-overlap
+
diff --git a/src/tests/vm/child-inherit.c b/src/tests/vm/child-inherit.c
new file mode 100644
index 0000000..d3186a1
--- /dev/null
+++ b/src/tests/vm/child-inherit.c
@@ -0,0 +1,16 @@
+/* Child process for mmap-inherit test.
+ Tries to write to a mapping present in the parent.
+ The process must be terminated with -1 exit code. */
+
+#include <string.h>
+#include "tests/vm/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ memset ((char *) 0x54321000, 0, 4096);
+ fail ("child can modify parent's memory mappings");
+}
+
diff --git a/src/tests/vm/child-linear.c b/src/tests/vm/child-linear.c
new file mode 100644
index 0000000..eca3e3f
--- /dev/null
+++ b/src/tests/vm/child-linear.c
@@ -0,0 +1,36 @@
+/* Child process of page-parallel.
+ Encrypts 1 MB of zeros, then decrypts it, and ensures that
+ the zeros are back. */
+
+#include <string.h>
+#include "tests/arc4.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+const char *test_name = "child-linear";
+
+#define SIZE (1024 * 1024)
+static char buf[SIZE];
+
+int
+main (int argc, char *argv[])
+{
+ const char *key = argv[argc - 1];
+ struct arc4 arc4;
+ size_t i;
+
+ /* Encrypt zeros. */
+ arc4_init (&arc4, key, strlen (key));
+ arc4_crypt (&arc4, buf, SIZE);
+
+ /* Decrypt back to zeros. */
+ arc4_init (&arc4, key, strlen (key));
+ arc4_crypt (&arc4, buf, SIZE);
+
+ /* Check that it's all zeros. */
+ for (i = 0; i < SIZE; i++)
+ if (buf[i] != '\0')
+ fail ("byte %zu != 0", i);
+
+ return 0x42;
+}
diff --git a/src/tests/vm/child-mm-wrt.c b/src/tests/vm/child-mm-wrt.c
new file mode 100644
index 0000000..8419788
--- /dev/null
+++ b/src/tests/vm/child-mm-wrt.c
@@ -0,0 +1,24 @@
+/* Child process of mmap-exit.
+ Mmaps a file and writes to it via the mmap'ing, then exits
+ without calling munmap. The data in the mapped region must be
+ written out at program termination. */
+
+#include <string.h>
+#include <syscall.h>
+#include "tests/vm/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#define ACTUAL ((void *) 0x10000000)
+
+void
+test_main (void)
+{
+ int handle;
+
+ CHECK (create ("sample.txt", sizeof sample), "create \"sample.txt\"");
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ CHECK (mmap (handle, ACTUAL) != MAP_FAILED, "mmap \"sample.txt\"");
+ memcpy (ACTUAL, sample, sizeof sample);
+}
+
diff --git a/src/tests/vm/child-qsort-mm.c b/src/tests/vm/child-qsort-mm.c
new file mode 100644
index 0000000..db45499
--- /dev/null
+++ b/src/tests/vm/child-qsort-mm.c
@@ -0,0 +1,25 @@
+/* Mmaps a 128 kB file "sorts" the bytes in it, using quick sort,
+ a multi-pass divide and conquer algorithm. */
+
+#include <debug.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+#include "tests/vm/qsort.h"
+
+const char *test_name = "child-qsort-mm";
+
+int
+main (int argc UNUSED, char *argv[])
+{
+ int handle;
+ unsigned char *p = (unsigned char *) 0x10000000;
+
+ quiet = true;
+
+ CHECK ((handle = open (argv[1])) > 1, "open \"%s\"", argv[1]);
+ CHECK (mmap (handle, p) != MAP_FAILED, "mmap \"%s\"", argv[1]);
+ qsort_bytes (p, 1024 * 128);
+
+ return 80;
+}
diff --git a/src/tests/vm/child-qsort.c b/src/tests/vm/child-qsort.c
new file mode 100644
index 0000000..355f4eb
--- /dev/null
+++ b/src/tests/vm/child-qsort.c
@@ -0,0 +1,32 @@
+/* Reads a 128 kB file onto the stack and "sorts" the bytes in
+ it, using quick sort, a multi-pass divide and conquer
+ algorithm. The sorted data is written back to the same file
+ in-place. */
+
+#include <debug.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+#include "tests/vm/qsort.h"
+
+const char *test_name = "child-qsort";
+
+int
+main (int argc UNUSED, char *argv[])
+{
+ int handle;
+ unsigned char buf[128 * 1024];
+ size_t size;
+
+ quiet = true;
+
+ CHECK ((handle = open (argv[1])) > 1, "open \"%s\"", argv[1]);
+
+ size = read (handle, buf, sizeof buf);
+ qsort_bytes (buf, sizeof buf);
+ seek (handle, 0);
+ write (handle, buf, size);
+ close (handle);
+
+ return 72;
+}
diff --git a/src/tests/vm/child-sort.c b/src/tests/vm/child-sort.c
new file mode 100644
index 0000000..dff2c77
--- /dev/null
+++ b/src/tests/vm/child-sort.c
@@ -0,0 +1,42 @@
+/* Reads a 128 kB file into static data and "sorts" the bytes in
+ it, using counting sort, a single-pass algorithm. The sorted
+ data is written back to the same file in-place. */
+
+#include <debug.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+const char *test_name = "child-sort";
+
+unsigned char buf[128 * 1024];
+size_t histogram[256];
+
+int
+main (int argc UNUSED, char *argv[])
+{
+ int handle;
+ unsigned char *p;
+ size_t size;
+ size_t i;
+
+ quiet = true;
+
+ CHECK ((handle = open (argv[1])) > 1, "open \"%s\"", argv[1]);
+
+ size = read (handle, buf, sizeof buf);
+ for (i = 0; i < size; i++)
+ histogram[buf[i]]++;
+ p = buf;
+ for (i = 0; i < sizeof histogram / sizeof *histogram; i++)
+ {
+ size_t j = histogram[i];
+ while (j-- > 0)
+ *p++ = i;
+ }
+ seek (handle, 0);
+ write (handle, buf, size);
+ close (handle);
+
+ return 123;
+}
diff --git a/src/tests/vm/mmap-bad-fd.c b/src/tests/vm/mmap-bad-fd.c
new file mode 100644
index 0000000..76a7b50
--- /dev/null
+++ b/src/tests/vm/mmap-bad-fd.c
@@ -0,0 +1,15 @@
+/* Tries to mmap an invalid fd,
+ which must either fail silently or terminate the process with
+ exit code -1. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ CHECK (mmap (0x5678, (void *) 0x10000000) == MAP_FAILED,
+ "try to mmap invalid fd");
+}
+
diff --git a/src/tests/vm/mmap-bad-fd.ck b/src/tests/vm/mmap-bad-fd.ck
new file mode 100644
index 0000000..f3f58d5
--- /dev/null
+++ b/src/tests/vm/mmap-bad-fd.ck
@@ -0,0 +1,15 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(mmap-bad-fd) begin
+(mmap-bad-fd) try to mmap invalid fd
+(mmap-bad-fd) end
+mmap-bad-fd: exit(0)
+EOF
+(mmap-bad-fd) begin
+(mmap-bad-fd) try to mmap invalid fd
+mmap-bad-fd: exit(-1)
+EOF
+pass;
diff --git a/src/tests/vm/mmap-clean.c b/src/tests/vm/mmap-clean.c
new file mode 100644
index 0000000..ea1dc9c
--- /dev/null
+++ b/src/tests/vm/mmap-clean.c
@@ -0,0 +1,53 @@
+/* Verifies that mmap'd regions are only written back on munmap
+ if the data was actually modified in memory. */
+
+#include <string.h>
+#include <syscall.h>
+#include "tests/vm/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ static const char overwrite[] = "Now is the time for all good...";
+ static char buffer[sizeof sample - 1];
+ char *actual = (char *) 0x54321000;
+ int handle;
+ mapid_t map;
+
+ /* Open file, map, verify data. */
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ CHECK ((map = mmap (handle, actual)) != MAP_FAILED, "mmap \"sample.txt\"");
+ if (memcmp (actual, sample, strlen (sample)))
+ fail ("read of mmap'd file reported bad data");
+
+ /* Modify file. */
+ CHECK (write (handle, overwrite, strlen (overwrite))
+ == (int) strlen (overwrite),
+ "write \"sample.txt\"");
+
+ /* Close mapping. Data should not be written back, because we
+ didn't modify it via the mapping. */
+ msg ("munmap \"sample.txt\"");
+ munmap (map);
+
+ /* Read file back. */
+ msg ("seek \"sample.txt\"");
+ seek (handle, 0);
+ CHECK (read (handle, buffer, sizeof buffer) == sizeof buffer,
+ "read \"sample.txt\"");
+
+ /* Verify that file overwrite worked. */
+ if (memcmp (buffer, overwrite, strlen (overwrite))
+ || memcmp (buffer + strlen (overwrite), sample + strlen (overwrite),
+ strlen (sample) - strlen (overwrite)))
+ {
+ if (!memcmp (buffer, sample, strlen (sample)))
+ fail ("munmap wrote back clean page");
+ else
+ fail ("read surprising data from file");
+ }
+ else
+ msg ("file change was retained after munmap");
+}
diff --git a/src/tests/vm/mmap-clean.ck b/src/tests/vm/mmap-clean.ck
new file mode 100644
index 0000000..1666d6c
--- /dev/null
+++ b/src/tests/vm/mmap-clean.ck
@@ -0,0 +1,16 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-clean) begin
+(mmap-clean) open "sample.txt"
+(mmap-clean) mmap "sample.txt"
+(mmap-clean) write "sample.txt"
+(mmap-clean) munmap "sample.txt"
+(mmap-clean) seek "sample.txt"
+(mmap-clean) read "sample.txt"
+(mmap-clean) file change was retained after munmap
+(mmap-clean) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-close.c b/src/tests/vm/mmap-close.c
new file mode 100644
index 0000000..d016ee3
--- /dev/null
+++ b/src/tests/vm/mmap-close.c
@@ -0,0 +1,27 @@
+/* Verifies that memory mappings persist after file close. */
+
+#include <string.h>
+#include <syscall.h>
+#include "tests/vm/sample.inc"
+#include "tests/arc4.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#define ACTUAL ((void *) 0x10000000)
+
+void
+test_main (void)
+{
+ int handle;
+ mapid_t map;
+
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ CHECK ((map = mmap (handle, ACTUAL)) != MAP_FAILED, "mmap \"sample.txt\"");
+
+ close (handle);
+
+ if (memcmp (ACTUAL, sample, strlen (sample)))
+ fail ("read of mmap'd file reported bad data");
+
+ munmap (map);
+}
diff --git a/src/tests/vm/mmap-close.ck b/src/tests/vm/mmap-close.ck
new file mode 100644
index 0000000..d15e41a
--- /dev/null
+++ b/src/tests/vm/mmap-close.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-close) begin
+(mmap-close) open "sample.txt"
+(mmap-close) mmap "sample.txt"
+(mmap-close) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-exit.c b/src/tests/vm/mmap-exit.c
new file mode 100644
index 0000000..7a2278a
--- /dev/null
+++ b/src/tests/vm/mmap-exit.c
@@ -0,0 +1,22 @@
+/* Executes child-mm-wrt and verifies that the writes that should
+ have occurred really did. */
+
+#include <syscall.h>
+#include "tests/vm/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ pid_t child;
+
+ /* Make child write file. */
+ quiet = true;
+ CHECK ((child = exec ("child-mm-wrt")) != -1, "exec \"child-mm-wrt\"");
+ CHECK (wait (child) == 0, "wait for child (should return 0)");
+ quiet = false;
+
+ /* Check file contents. */
+ check_file ("sample.txt", sample, sizeof sample);
+}
diff --git a/src/tests/vm/mmap-exit.ck b/src/tests/vm/mmap-exit.ck
new file mode 100644
index 0000000..457d34a
--- /dev/null
+++ b/src/tests/vm/mmap-exit.ck
@@ -0,0 +1,17 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-exit) begin
+(child-mm-wrt) begin
+(child-mm-wrt) create "sample.txt"
+(child-mm-wrt) open "sample.txt"
+(child-mm-wrt) mmap "sample.txt"
+(child-mm-wrt) end
+(mmap-exit) open "sample.txt" for verification
+(mmap-exit) verified contents of "sample.txt"
+(mmap-exit) close "sample.txt"
+(mmap-exit) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-inherit.c b/src/tests/vm/mmap-inherit.c
new file mode 100644
index 0000000..7fa9607
--- /dev/null
+++ b/src/tests/vm/mmap-inherit.c
@@ -0,0 +1,32 @@
+/* Maps a file into memory and runs child-inherit to verify that
+ mappings are not inherited. */
+
+#include <string.h>
+#include <syscall.h>
+#include "tests/vm/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ char *actual = (char *) 0x54321000;
+ int handle;
+ pid_t child;
+
+ /* Open file, map, verify data. */
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ CHECK (mmap (handle, actual) != MAP_FAILED, "mmap \"sample.txt\"");
+ if (memcmp (actual, sample, strlen (sample)))
+ fail ("read of mmap'd file reported bad data");
+
+ /* Spawn child and wait. */
+ CHECK ((child = exec ("child-inherit")) != -1, "exec \"child-inherit\"");
+ quiet = true;
+ CHECK (wait (child) == -1, "wait for child (should return -1)");
+ quiet = false;
+
+ /* Verify data again. */
+ CHECK (!memcmp (actual, sample, strlen (sample)),
+ "checking that mmap'd file still has same data");
+}
diff --git a/src/tests/vm/mmap-inherit.ck b/src/tests/vm/mmap-inherit.ck
new file mode 100644
index 0000000..7e69122
--- /dev/null
+++ b/src/tests/vm/mmap-inherit.ck
@@ -0,0 +1,16 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ( [<<'EOF']);
+(mmap-inherit) begin
+(mmap-inherit) open "sample.txt"
+(mmap-inherit) mmap "sample.txt"
+(mmap-inherit) exec "child-inherit"
+(child-inherit) begin
+child-inherit: exit(-1)
+(mmap-inherit) checking that mmap'd file still has same data
+(mmap-inherit) end
+mmap-inherit: exit(0)
+EOF
+pass;
diff --git a/src/tests/vm/mmap-misalign.c b/src/tests/vm/mmap-misalign.c
new file mode 100644
index 0000000..34141a9
--- /dev/null
+++ b/src/tests/vm/mmap-misalign.c
@@ -0,0 +1,16 @@
+/* Verifies that misaligned memory mappings are disallowed. */
+
+#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\"");
+ CHECK (mmap (handle, (void *) 0x10001234) == MAP_FAILED,
+ "try to mmap at misaligned address");
+}
+
diff --git a/src/tests/vm/mmap-misalign.ck b/src/tests/vm/mmap-misalign.ck
new file mode 100644
index 0000000..145a2e8
--- /dev/null
+++ b/src/tests/vm/mmap-misalign.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-misalign) begin
+(mmap-misalign) open "sample.txt"
+(mmap-misalign) try to mmap at misaligned address
+(mmap-misalign) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-null.c b/src/tests/vm/mmap-null.c
new file mode 100644
index 0000000..f8ef075
--- /dev/null
+++ b/src/tests/vm/mmap-null.c
@@ -0,0 +1,15 @@
+/* Verifies that memory mappings at address 0 are disallowed. */
+
+#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\"");
+ CHECK (mmap (handle, NULL) == MAP_FAILED, "try to mmap at address 0");
+}
+
diff --git a/src/tests/vm/mmap-null.ck b/src/tests/vm/mmap-null.ck
new file mode 100644
index 0000000..aacdd65
--- /dev/null
+++ b/src/tests/vm/mmap-null.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-null) begin
+(mmap-null) open "sample.txt"
+(mmap-null) try to mmap at address 0
+(mmap-null) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-over-code.c b/src/tests/vm/mmap-over-code.c
new file mode 100644
index 0000000..d3619a3
--- /dev/null
+++ b/src/tests/vm/mmap-over-code.c
@@ -0,0 +1,19 @@
+/* Verifies that mapping over the code segment is disallowed. */
+
+#include <stdint.h>
+#include <round.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ uintptr_t test_main_page = ROUND_DOWN ((uintptr_t) test_main, 4096);
+ int handle;
+
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ CHECK (mmap (handle, (void *) test_main_page) == MAP_FAILED,
+ "try to mmap over code segment");
+}
+
diff --git a/src/tests/vm/mmap-over-code.ck b/src/tests/vm/mmap-over-code.ck
new file mode 100644
index 0000000..b5b23c7
--- /dev/null
+++ b/src/tests/vm/mmap-over-code.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-over-code) begin
+(mmap-over-code) open "sample.txt"
+(mmap-over-code) try to mmap over code segment
+(mmap-over-code) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-over-data.c b/src/tests/vm/mmap-over-data.c
new file mode 100644
index 0000000..9ea5d49
--- /dev/null
+++ b/src/tests/vm/mmap-over-data.c
@@ -0,0 +1,21 @@
+/* Verifies that mapping over the data segment is disallowed. */
+
+#include <stdint.h>
+#include <round.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+static char x;
+
+void
+test_main (void)
+{
+ uintptr_t x_page = ROUND_DOWN ((uintptr_t) &x, 4096);
+ int handle;
+
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ CHECK (mmap (handle, (void *) x_page) == MAP_FAILED,
+ "try to mmap over data segment");
+}
+
diff --git a/src/tests/vm/mmap-over-data.ck b/src/tests/vm/mmap-over-data.ck
new file mode 100644
index 0000000..98770cc
--- /dev/null
+++ b/src/tests/vm/mmap-over-data.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-over-data) begin
+(mmap-over-data) open "sample.txt"
+(mmap-over-data) try to mmap over data segment
+(mmap-over-data) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-over-stk.c b/src/tests/vm/mmap-over-stk.c
new file mode 100644
index 0000000..4e241e8
--- /dev/null
+++ b/src/tests/vm/mmap-over-stk.c
@@ -0,0 +1,19 @@
+/* Verifies that mapping over the stack segment is disallowed. */
+
+#include <stdint.h>
+#include <round.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ int handle;
+ uintptr_t handle_page = ROUND_DOWN ((uintptr_t) &handle, 4096);
+
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ CHECK (mmap (handle, (void *) handle_page) == MAP_FAILED,
+ "try to mmap over stack segment");
+}
+
diff --git a/src/tests/vm/mmap-over-stk.ck b/src/tests/vm/mmap-over-stk.ck
new file mode 100644
index 0000000..e6880cf
--- /dev/null
+++ b/src/tests/vm/mmap-over-stk.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-over-stk) begin
+(mmap-over-stk) open "sample.txt"
+(mmap-over-stk) try to mmap over stack segment
+(mmap-over-stk) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-overlap.c b/src/tests/vm/mmap-overlap.c
new file mode 100644
index 0000000..668ae5f
--- /dev/null
+++ b/src/tests/vm/mmap-overlap.c
@@ -0,0 +1,20 @@
+/* Verifies that overlapping memory mappings are disallowed. */
+
+#include <syscall.h>
+#include "tests/vm/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ char *start = (char *) 0x10000000;
+ int fd[2];
+
+ CHECK ((fd[0] = open ("zeros")) > 1, "open \"zeros\" once");
+ CHECK (mmap (fd[0], start) != MAP_FAILED, "mmap \"zeros\"");
+ CHECK ((fd[1] = open ("zeros")) > 1 && fd[0] != fd[1],
+ "open \"zeros\" again");
+ CHECK (mmap (fd[1], start + 4096) == MAP_FAILED,
+ "try to mmap \"zeros\" again");
+}
diff --git a/src/tests/vm/mmap-overlap.ck b/src/tests/vm/mmap-overlap.ck
new file mode 100644
index 0000000..f13801e
--- /dev/null
+++ b/src/tests/vm/mmap-overlap.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-overlap) begin
+(mmap-overlap) open "zeros" once
+(mmap-overlap) mmap "zeros"
+(mmap-overlap) open "zeros" again
+(mmap-overlap) try to mmap "zeros" again
+(mmap-overlap) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-read.c b/src/tests/vm/mmap-read.c
new file mode 100644
index 0000000..c0f23a1
--- /dev/null
+++ b/src/tests/vm/mmap-read.c
@@ -0,0 +1,32 @@
+/* Uses a memory mapping to read a file. */
+
+#include <string.h>
+#include <syscall.h>
+#include "tests/vm/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ char *actual = (char *) 0x10000000;
+ int handle;
+ mapid_t map;
+ size_t i;
+
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ CHECK ((map = mmap (handle, actual)) != MAP_FAILED, "mmap \"sample.txt\"");
+
+ /* Check that data is correct. */
+ if (memcmp (actual, sample, strlen (sample)))
+ fail ("read of mmap'd file reported bad data");
+
+ /* Verify that data is followed by zeros. */
+ for (i = strlen (sample); i < 4096; i++)
+ if (actual[i] != 0)
+ fail ("byte %zu of mmap'd region has value %02hhx (should be 0)",
+ i, actual[i]);
+
+ munmap (map);
+ close (handle);
+}
diff --git a/src/tests/vm/mmap-read.ck b/src/tests/vm/mmap-read.ck
new file mode 100644
index 0000000..95ab790
--- /dev/null
+++ b/src/tests/vm/mmap-read.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-read) begin
+(mmap-read) open "sample.txt"
+(mmap-read) mmap "sample.txt"
+(mmap-read) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-remove.c b/src/tests/vm/mmap-remove.c
new file mode 100644
index 0000000..5f7444d
--- /dev/null
+++ b/src/tests/vm/mmap-remove.c
@@ -0,0 +1,43 @@
+/* Deletes and closes file that is mapped into memory
+ and verifies that it can still be read through the mapping. */
+
+#include <string.h>
+#include <syscall.h>
+#include "tests/vm/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ char *actual = (char *) 0x10000000;
+ int handle;
+ mapid_t map;
+ size_t i;
+
+ /* Map file. */
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ CHECK ((map = mmap (handle, actual)) != MAP_FAILED, "mmap \"sample.txt\"");
+
+ /* Close file and delete it. */
+ close (handle);
+ CHECK (remove ("sample.txt"), "remove \"sample.txt\"");
+ CHECK (open ("sample.txt") == -1, "try to open \"sample.txt\"");
+
+ /* Create a new file in hopes of overwriting data from the old
+ one, in case the file system has incorrectly freed the
+ file's data. */
+ CHECK (create ("another", 4096 * 10), "create \"another\"");
+
+ /* Check that mapped data is correct. */
+ if (memcmp (actual, sample, strlen (sample)))
+ fail ("read of mmap'd file reported bad data");
+
+ /* Verify that data is followed by zeros. */
+ for (i = strlen (sample); i < 4096; i++)
+ if (actual[i] != 0)
+ fail ("byte %zu of mmap'd region has value %02hhx (should be 0)",
+ i, actual[i]);
+
+ munmap (map);
+}
diff --git a/src/tests/vm/mmap-remove.ck b/src/tests/vm/mmap-remove.ck
new file mode 100644
index 0000000..d3cc938
--- /dev/null
+++ b/src/tests/vm/mmap-remove.ck
@@ -0,0 +1,14 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-remove) begin
+(mmap-remove) open "sample.txt"
+(mmap-remove) mmap "sample.txt"
+(mmap-remove) remove "sample.txt"
+(mmap-remove) try to open "sample.txt"
+(mmap-remove) create "another"
+(mmap-remove) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-shuffle.c b/src/tests/vm/mmap-shuffle.c
new file mode 100644
index 0000000..29921ad
--- /dev/null
+++ b/src/tests/vm/mmap-shuffle.c
@@ -0,0 +1,38 @@
+/* Creates a 128 kB file and repeatedly shuffles data in it
+ through a memory mapping. */
+
+#include <stdio.h>
+#include <string.h>
+#include <syscall.h>
+#include "tests/arc4.h"
+#include "tests/cksum.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#define SIZE (128 * 1024)
+
+static char *buf = (char *) 0x10000000;
+
+void
+test_main (void)
+{
+ size_t i;
+ int handle;
+
+ /* Create file, mmap. */
+ CHECK (create ("buffer", SIZE), "create \"buffer\"");
+ CHECK ((handle = open ("buffer")) > 1, "open \"buffer\"");
+ CHECK (mmap (handle, buf) != MAP_FAILED, "mmap \"buffer\"");
+
+ /* Initialize. */
+ for (i = 0; i < SIZE; i++)
+ buf[i] = i * 257;
+ msg ("init: cksum=%lu", cksum (buf, SIZE));
+
+ /* Shuffle repeatedly. */
+ for (i = 0; i < 10; i++)
+ {
+ shuffle (buf, SIZE, 1);
+ msg ("shuffle %zu: cksum=%lu", i, cksum (buf, SIZE));
+ }
+}
diff --git a/src/tests/vm/mmap-shuffle.ck b/src/tests/vm/mmap-shuffle.ck
new file mode 100644
index 0000000..c158301
--- /dev/null
+++ b/src/tests/vm/mmap-shuffle.ck
@@ -0,0 +1,47 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::cksum;
+use tests::lib;
+
+my ($init, @shuffle);
+if (1) {
+ # Use precalculated values.
+ $init = 3115322833;
+ @shuffle = (1691062564, 1973575879, 1647619479, 96566261, 3885786467,
+ 3022003332, 3614934266, 2704001777, 735775156, 1864109763);
+} else {
+ # Recalculate values.
+ my ($buf) = "";
+ for my $i (0...128 * 1024 - 1) {
+ $buf .= chr (($i * 257) & 0xff);
+ }
+ $init = cksum ($buf);
+
+ random_init (0);
+ for my $i (1...10) {
+ $buf = shuffle ($buf, length ($buf), 1);
+ push (@shuffle, cksum ($buf));
+ }
+}
+
+check_expected (IGNORE_EXIT_CODES => 1, [<<EOF]);
+(mmap-shuffle) begin
+(mmap-shuffle) create "buffer"
+(mmap-shuffle) open "buffer"
+(mmap-shuffle) mmap "buffer"
+(mmap-shuffle) init: cksum=$init
+(mmap-shuffle) shuffle 0: cksum=$shuffle[0]
+(mmap-shuffle) shuffle 1: cksum=$shuffle[1]
+(mmap-shuffle) shuffle 2: cksum=$shuffle[2]
+(mmap-shuffle) shuffle 3: cksum=$shuffle[3]
+(mmap-shuffle) shuffle 4: cksum=$shuffle[4]
+(mmap-shuffle) shuffle 5: cksum=$shuffle[5]
+(mmap-shuffle) shuffle 6: cksum=$shuffle[6]
+(mmap-shuffle) shuffle 7: cksum=$shuffle[7]
+(mmap-shuffle) shuffle 8: cksum=$shuffle[8]
+(mmap-shuffle) shuffle 9: cksum=$shuffle[9]
+(mmap-shuffle) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-twice.c b/src/tests/vm/mmap-twice.c
new file mode 100644
index 0000000..d277a37
--- /dev/null
+++ b/src/tests/vm/mmap-twice.c
@@ -0,0 +1,28 @@
+/* Maps the same file into memory twice and verifies that the
+ same data is readable in both. */
+
+#include <string.h>
+#include <syscall.h>
+#include "tests/vm/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ char *actual[2] = {(char *) 0x10000000, (char *) 0x20000000};
+ size_t i;
+ int handle[2];
+
+ for (i = 0; i < 2; i++)
+ {
+ CHECK ((handle[i] = open ("sample.txt")) > 1,
+ "open \"sample.txt\" #%zu", i);
+ CHECK (mmap (handle[i], actual[i]) != MAP_FAILED,
+ "mmap \"sample.txt\" #%zu at %p", i, (void *) actual[i]);
+ }
+
+ for (i = 0; i < 2; i++)
+ CHECK (!memcmp (actual[i], sample, strlen (sample)),
+ "compare mmap'd file %zu against data", i);
+}
diff --git a/src/tests/vm/mmap-twice.ck b/src/tests/vm/mmap-twice.ck
new file mode 100644
index 0000000..05e9724
--- /dev/null
+++ b/src/tests/vm/mmap-twice.ck
@@ -0,0 +1,15 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-twice) begin
+(mmap-twice) open "sample.txt" #0
+(mmap-twice) mmap "sample.txt" #0 at 0x10000000
+(mmap-twice) open "sample.txt" #1
+(mmap-twice) mmap "sample.txt" #1 at 0x20000000
+(mmap-twice) compare mmap'd file 0 against data
+(mmap-twice) compare mmap'd file 1 against data
+(mmap-twice) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-unmap.c b/src/tests/vm/mmap-unmap.c
new file mode 100644
index 0000000..d35a79e
--- /dev/null
+++ b/src/tests/vm/mmap-unmap.c
@@ -0,0 +1,23 @@
+/* Maps and unmaps a file and verifies that the mapped region is
+ inaccessible afterward. */
+
+#include <syscall.h>
+#include "tests/vm/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#define ACTUAL ((void *) 0x10000000)
+
+void
+test_main (void)
+{
+ int handle;
+ mapid_t map;
+
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ CHECK ((map = mmap (handle, ACTUAL)) != MAP_FAILED, "mmap \"sample.txt\"");
+
+ munmap (map);
+
+ fail ("unmapped memory is readable (%d)", *(int *) ACTUAL);
+}
diff --git a/src/tests/vm/mmap-unmap.ck b/src/tests/vm/mmap-unmap.ck
new file mode 100644
index 0000000..119658c
--- /dev/null
+++ b/src/tests/vm/mmap-unmap.ck
@@ -0,0 +1,7 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::vm::process_death;
+
+check_process_death ('mmap-unmap');
diff --git a/src/tests/vm/mmap-write.c b/src/tests/vm/mmap-write.c
new file mode 100644
index 0000000..46e8043
--- /dev/null
+++ b/src/tests/vm/mmap-write.c
@@ -0,0 +1,32 @@
+/* Writes to a file through a mapping, and unmaps the file,
+ then reads the data in the file back using the read system
+ call to verify. */
+
+#include <string.h>
+#include <syscall.h>
+#include "tests/vm/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#define ACTUAL ((void *) 0x10000000)
+
+void
+test_main (void)
+{
+ int handle;
+ mapid_t map;
+ char buf[1024];
+
+ /* Write file via mmap. */
+ CHECK (create ("sample.txt", strlen (sample)), "create \"sample.txt\"");
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ CHECK ((map = mmap (handle, ACTUAL)) != MAP_FAILED, "mmap \"sample.txt\"");
+ memcpy (ACTUAL, sample, strlen (sample));
+ munmap (map);
+
+ /* Read back via read(). */
+ read (handle, buf, strlen (sample));
+ CHECK (!memcmp (buf, sample, strlen (sample)),
+ "compare read data against written data");
+ close (handle);
+}
diff --git a/src/tests/vm/mmap-write.ck b/src/tests/vm/mmap-write.ck
new file mode 100644
index 0000000..d2c9cc5
--- /dev/null
+++ b/src/tests/vm/mmap-write.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-write) begin
+(mmap-write) create "sample.txt"
+(mmap-write) open "sample.txt"
+(mmap-write) mmap "sample.txt"
+(mmap-write) compare read data against written data
+(mmap-write) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-zero.c b/src/tests/vm/mmap-zero.c
new file mode 100644
index 0000000..368b759
--- /dev/null
+++ b/src/tests/vm/mmap-zero.c
@@ -0,0 +1,27 @@
+/* Tries to map a zero-length file, which may or may not work but
+ should not terminate the process or crash.
+ Then dereferences the address that we tried to map,
+ and the process must be terminated with -1 exit code. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ char *data = (char *) 0x7f000000;
+ int handle;
+
+ CHECK (create ("empty", 0), "create empty file \"empty\"");
+ CHECK ((handle = open ("empty")) > 1, "open \"empty\"");
+
+ /* Calling mmap() might succeed or fail. We don't care. */
+ msg ("mmap \"empty\"");
+ mmap (handle, data);
+
+ /* Regardless of whether the call worked, *data should cause
+ the process to be terminated. */
+ fail ("unmapped memory is readable (%d)", *data);
+}
+
diff --git a/src/tests/vm/mmap-zero.ck b/src/tests/vm/mmap-zero.ck
new file mode 100644
index 0000000..0130fbd
--- /dev/null
+++ b/src/tests/vm/mmap-zero.ck
@@ -0,0 +1,12 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(mmap-zero) begin
+(mmap-zero) create empty file "empty"
+(mmap-zero) open "empty"
+(mmap-zero) mmap "empty"
+mmap-zero: exit(-1)
+EOF
+pass;
diff --git a/src/tests/vm/page-linear.c b/src/tests/vm/page-linear.c
new file mode 100644
index 0000000..652a47b
--- /dev/null
+++ b/src/tests/vm/page-linear.c
@@ -0,0 +1,44 @@
+/* Encrypts, then decrypts, 2 MB of memory and verifies that the
+ values are as they should be. */
+
+#include <string.h>
+#include "tests/arc4.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#define SIZE (2 * 1024 * 1024)
+
+static char buf[SIZE];
+
+void
+test_main (void)
+{
+ struct arc4 arc4;
+ size_t i;
+
+ /* Initialize to 0x5a. */
+ msg ("initialize");
+ memset (buf, 0x5a, sizeof buf);
+
+ /* Check that it's all 0x5a. */
+ msg ("read pass");
+ for (i = 0; i < SIZE; i++)
+ if (buf[i] != 0x5a)
+ fail ("byte %zu != 0x5a", i);
+
+ /* Encrypt zeros. */
+ msg ("read/modify/write pass one");
+ arc4_init (&arc4, "foobar", 6);
+ arc4_crypt (&arc4, buf, SIZE);
+
+ /* Decrypt back to zeros. */
+ msg ("read/modify/write pass two");
+ arc4_init (&arc4, "foobar", 6);
+ arc4_crypt (&arc4, buf, SIZE);
+
+ /* Check that it's all 0x5a. */
+ msg ("read pass");
+ for (i = 0; i < SIZE; i++)
+ if (buf[i] != 0x5a)
+ fail ("byte %zu != 0x5a", i);
+}
diff --git a/src/tests/vm/page-linear.ck b/src/tests/vm/page-linear.ck
new file mode 100644
index 0000000..dcbc884
--- /dev/null
+++ b/src/tests/vm/page-linear.ck
@@ -0,0 +1,14 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(page-linear) begin
+(page-linear) initialize
+(page-linear) read pass
+(page-linear) read/modify/write pass one
+(page-linear) read/modify/write pass two
+(page-linear) read pass
+(page-linear) end
+EOF
+pass;
diff --git a/src/tests/vm/page-merge-mm.c b/src/tests/vm/page-merge-mm.c
new file mode 100644
index 0000000..908c71c
--- /dev/null
+++ b/src/tests/vm/page-merge-mm.c
@@ -0,0 +1,8 @@
+#include "tests/main.h"
+#include "tests/vm/parallel-merge.h"
+
+void
+test_main (void)
+{
+ parallel_merge ("child-qsort-mm", 80);
+}
diff --git a/src/tests/vm/page-merge-mm.ck b/src/tests/vm/page-merge-mm.ck
new file mode 100644
index 0000000..74fa980
--- /dev/null
+++ b/src/tests/vm/page-merge-mm.ck
@@ -0,0 +1,29 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(page-merge-mm) begin
+(page-merge-mm) init
+(page-merge-mm) sort chunk 0
+(page-merge-mm) sort chunk 1
+(page-merge-mm) sort chunk 2
+(page-merge-mm) sort chunk 3
+(page-merge-mm) sort chunk 4
+(page-merge-mm) sort chunk 5
+(page-merge-mm) sort chunk 6
+(page-merge-mm) sort chunk 7
+(page-merge-mm) wait for child 0
+(page-merge-mm) wait for child 1
+(page-merge-mm) wait for child 2
+(page-merge-mm) wait for child 3
+(page-merge-mm) wait for child 4
+(page-merge-mm) wait for child 5
+(page-merge-mm) wait for child 6
+(page-merge-mm) wait for child 7
+(page-merge-mm) merge
+(page-merge-mm) verify
+(page-merge-mm) success, buf_idx=1,048,576
+(page-merge-mm) end
+EOF
+pass;
diff --git a/src/tests/vm/page-merge-par.c b/src/tests/vm/page-merge-par.c
new file mode 100644
index 0000000..e7e1609
--- /dev/null
+++ b/src/tests/vm/page-merge-par.c
@@ -0,0 +1,8 @@
+#include "tests/main.h"
+#include "tests/vm/parallel-merge.h"
+
+void
+test_main (void)
+{
+ parallel_merge ("child-sort", 123);
+}
diff --git a/src/tests/vm/page-merge-par.ck b/src/tests/vm/page-merge-par.ck
new file mode 100644
index 0000000..31f8aa7
--- /dev/null
+++ b/src/tests/vm/page-merge-par.ck
@@ -0,0 +1,29 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(page-merge-par) begin
+(page-merge-par) init
+(page-merge-par) sort chunk 0
+(page-merge-par) sort chunk 1
+(page-merge-par) sort chunk 2
+(page-merge-par) sort chunk 3
+(page-merge-par) sort chunk 4
+(page-merge-par) sort chunk 5
+(page-merge-par) sort chunk 6
+(page-merge-par) sort chunk 7
+(page-merge-par) wait for child 0
+(page-merge-par) wait for child 1
+(page-merge-par) wait for child 2
+(page-merge-par) wait for child 3
+(page-merge-par) wait for child 4
+(page-merge-par) wait for child 5
+(page-merge-par) wait for child 6
+(page-merge-par) wait for child 7
+(page-merge-par) merge
+(page-merge-par) verify
+(page-merge-par) success, buf_idx=1,048,576
+(page-merge-par) end
+EOF
+pass;
diff --git a/src/tests/vm/page-merge-seq.c b/src/tests/vm/page-merge-seq.c
new file mode 100644
index 0000000..12e3880
--- /dev/null
+++ b/src/tests/vm/page-merge-seq.c
@@ -0,0 +1,137 @@
+/* Generates about 1 MB of random data that is then divided into
+ 16 chunks. A separate subprocess sorts each chunk in
+ sequence. Then we merge the chunks and verify that the result
+ is what it should be. */
+
+#include <syscall.h>
+#include "tests/arc4.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+/* This is the max file size for an older version of the Pintos
+ file system that had 126 direct blocks each pointing to a
+ single disk sector. We could raise it now. */
+#define CHUNK_SIZE (126 * 512)
+#define CHUNK_CNT 16 /* Number of chunks. */
+#define DATA_SIZE (CHUNK_CNT * CHUNK_SIZE) /* Buffer size. */
+
+unsigned char buf1[DATA_SIZE], buf2[DATA_SIZE];
+size_t histogram[256];
+
+/* Initialize buf1 with random data,
+ then count the number of instances of each value within it. */
+static void
+init (void)
+{
+ struct arc4 arc4;
+ size_t i;
+
+ msg ("init");
+
+ arc4_init (&arc4, "foobar", 6);
+ arc4_crypt (&arc4, buf1, sizeof buf1);
+ for (i = 0; i < sizeof buf1; i++)
+ histogram[buf1[i]]++;
+}
+
+/* Sort each chunk of buf1 using a subprocess. */
+static void
+sort_chunks (void)
+{
+ size_t i;
+
+ create ("buffer", CHUNK_SIZE);
+ for (i = 0; i < CHUNK_CNT; i++)
+ {
+ pid_t child;
+ int handle;
+
+ msg ("sort chunk %zu", i);
+
+ /* Write this chunk to a file. */
+ quiet = true;
+ CHECK ((handle = open ("buffer")) > 1, "open \"buffer\"");
+ write (handle, buf1 + CHUNK_SIZE * i, CHUNK_SIZE);
+ close (handle);
+
+ /* Sort with subprocess. */
+ CHECK ((child = exec ("child-sort buffer")) != -1,
+ "exec \"child-sort buffer\"");
+ CHECK (wait (child) == 123, "wait for child-sort");
+
+ /* Read chunk back from file. */
+ CHECK ((handle = open ("buffer")) > 1, "open \"buffer\"");
+ read (handle, buf1 + CHUNK_SIZE * i, CHUNK_SIZE);
+ close (handle);
+
+ quiet = false;
+ }
+}
+
+/* Merge the sorted chunks in buf1 into a fully sorted buf2. */
+static void
+merge (void)
+{
+ unsigned char *mp[CHUNK_CNT];
+ size_t mp_left;
+ unsigned char *op;
+ size_t i;
+
+ msg ("merge");
+
+ /* Initialize merge pointers. */
+ mp_left = CHUNK_CNT;
+ for (i = 0; i < CHUNK_CNT; i++)
+ mp[i] = buf1 + CHUNK_SIZE * i;
+
+ /* Merge. */
+ op = buf2;
+ while (mp_left > 0)
+ {
+ /* Find smallest value. */
+ size_t min = 0;
+ for (i = 1; i < mp_left; i++)
+ if (*mp[i] < *mp[min])
+ min = i;
+
+ /* Append value to buf2. */
+ *op++ = *mp[min];
+
+ /* Advance merge pointer.
+ Delete this chunk from the set if it's emptied. */
+ if ((++mp[min] - buf1) % CHUNK_SIZE == 0)
+ mp[min] = mp[--mp_left];
+ }
+}
+
+static void
+verify (void)
+{
+ size_t buf_idx;
+ size_t hist_idx;
+
+ msg ("verify");
+
+ buf_idx = 0;
+ for (hist_idx = 0; hist_idx < sizeof histogram / sizeof *histogram;
+ hist_idx++)
+ {
+ while (histogram[hist_idx]-- > 0)
+ {
+ if (buf2[buf_idx] != hist_idx)
+ fail ("bad value %d in byte %zu", buf2[buf_idx], buf_idx);
+ buf_idx++;
+ }
+ }
+
+ msg ("success, buf_idx=%'zu", buf_idx);
+}
+
+void
+test_main (void)
+{
+ init ();
+ sort_chunks ();
+ merge ();
+ verify ();
+}
diff --git a/src/tests/vm/page-merge-seq.ck b/src/tests/vm/page-merge-seq.ck
new file mode 100644
index 0000000..d78f69d
--- /dev/null
+++ b/src/tests/vm/page-merge-seq.ck
@@ -0,0 +1,29 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(page-merge-seq) begin
+(page-merge-seq) init
+(page-merge-seq) sort chunk 0
+(page-merge-seq) sort chunk 1
+(page-merge-seq) sort chunk 2
+(page-merge-seq) sort chunk 3
+(page-merge-seq) sort chunk 4
+(page-merge-seq) sort chunk 5
+(page-merge-seq) sort chunk 6
+(page-merge-seq) sort chunk 7
+(page-merge-seq) sort chunk 8
+(page-merge-seq) sort chunk 9
+(page-merge-seq) sort chunk 10
+(page-merge-seq) sort chunk 11
+(page-merge-seq) sort chunk 12
+(page-merge-seq) sort chunk 13
+(page-merge-seq) sort chunk 14
+(page-merge-seq) sort chunk 15
+(page-merge-seq) merge
+(page-merge-seq) verify
+(page-merge-seq) success, buf_idx=1,032,192
+(page-merge-seq) end
+EOF
+pass;
diff --git a/src/tests/vm/page-merge-stk.c b/src/tests/vm/page-merge-stk.c
new file mode 100644
index 0000000..5eb1069
--- /dev/null
+++ b/src/tests/vm/page-merge-stk.c
@@ -0,0 +1,8 @@
+#include "tests/main.h"
+#include "tests/vm/parallel-merge.h"
+
+void
+test_main (void)
+{
+ parallel_merge ("child-qsort", 72);
+}
diff --git a/src/tests/vm/page-merge-stk.ck b/src/tests/vm/page-merge-stk.ck
new file mode 100644
index 0000000..c5bc1ae
--- /dev/null
+++ b/src/tests/vm/page-merge-stk.ck
@@ -0,0 +1,29 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(page-merge-stk) begin
+(page-merge-stk) init
+(page-merge-stk) sort chunk 0
+(page-merge-stk) sort chunk 1
+(page-merge-stk) sort chunk 2
+(page-merge-stk) sort chunk 3
+(page-merge-stk) sort chunk 4
+(page-merge-stk) sort chunk 5
+(page-merge-stk) sort chunk 6
+(page-merge-stk) sort chunk 7
+(page-merge-stk) wait for child 0
+(page-merge-stk) wait for child 1
+(page-merge-stk) wait for child 2
+(page-merge-stk) wait for child 3
+(page-merge-stk) wait for child 4
+(page-merge-stk) wait for child 5
+(page-merge-stk) wait for child 6
+(page-merge-stk) wait for child 7
+(page-merge-stk) merge
+(page-merge-stk) verify
+(page-merge-stk) success, buf_idx=1,048,576
+(page-merge-stk) end
+EOF
+pass;
diff --git a/src/tests/vm/page-parallel.c b/src/tests/vm/page-parallel.c
new file mode 100644
index 0000000..9d619e0
--- /dev/null
+++ b/src/tests/vm/page-parallel.c
@@ -0,0 +1,21 @@
+/* Runs 4 child-linear processes at once. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#define CHILD_CNT 4
+
+void
+test_main (void)
+{
+ pid_t children[CHILD_CNT];
+ int i;
+
+ for (i = 0; i < CHILD_CNT; i++)
+ CHECK ((children[i] = exec ("child-linear")) != -1,
+ "exec \"child-linear\"");
+
+ for (i = 0; i < CHILD_CNT; i++)
+ CHECK (wait (children[i]) == 0x42, "wait for child %d", i);
+}
diff --git a/src/tests/vm/page-parallel.ck b/src/tests/vm/page-parallel.ck
new file mode 100644
index 0000000..90c14ef
--- /dev/null
+++ b/src/tests/vm/page-parallel.ck
@@ -0,0 +1,17 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(page-parallel) begin
+(page-parallel) exec "child-linear"
+(page-parallel) exec "child-linear"
+(page-parallel) exec "child-linear"
+(page-parallel) exec "child-linear"
+(page-parallel) wait for child 0
+(page-parallel) wait for child 1
+(page-parallel) wait for child 2
+(page-parallel) wait for child 3
+(page-parallel) end
+EOF
+pass;
diff --git a/src/tests/vm/page-shuffle.c b/src/tests/vm/page-shuffle.c
new file mode 100644
index 0000000..095a9da
--- /dev/null
+++ b/src/tests/vm/page-shuffle.c
@@ -0,0 +1,30 @@
+/* Shuffles a 128 kB data buffer 10 times, printing the checksum
+ after each time. */
+
+#include <stdbool.h>
+#include "tests/arc4.h"
+#include "tests/cksum.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#define SIZE (128 * 1024)
+
+static char buf[SIZE];
+
+void
+test_main (void)
+{
+ size_t i;
+
+ /* Initialize. */
+ for (i = 0; i < sizeof buf; i++)
+ buf[i] = i * 257;
+ msg ("init: cksum=%lu", cksum (buf, sizeof buf));
+
+ /* Shuffle repeatedly. */
+ for (i = 0; i < 10; i++)
+ {
+ shuffle (buf, sizeof buf, 1);
+ msg ("shuffle %zu: cksum=%lu", i, cksum (buf, sizeof buf));
+ }
+}
diff --git a/src/tests/vm/page-shuffle.ck b/src/tests/vm/page-shuffle.ck
new file mode 100644
index 0000000..6447d38
--- /dev/null
+++ b/src/tests/vm/page-shuffle.ck
@@ -0,0 +1,44 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::cksum;
+use tests::lib;
+
+my ($init, @shuffle);
+if (1) {
+ # Use precalculated values.
+ $init = 3115322833;
+ @shuffle = (1691062564, 1973575879, 1647619479, 96566261, 3885786467,
+ 3022003332, 3614934266, 2704001777, 735775156, 1864109763);
+} else {
+ # Recalculate values.
+ my ($buf) = "";
+ for my $i (0...128 * 1024 - 1) {
+ $buf .= chr (($i * 257) & 0xff);
+ }
+ $init = cksum ($buf);
+
+ random_init (0);
+ for my $i (1...10) {
+ $buf = shuffle ($buf, length ($buf), 1);
+ push (@shuffle, cksum ($buf));
+ }
+}
+
+check_expected (IGNORE_EXIT_CODES => 1, [<<EOF]);
+(page-shuffle) begin
+(page-shuffle) init: cksum=$init
+(page-shuffle) shuffle 0: cksum=$shuffle[0]
+(page-shuffle) shuffle 1: cksum=$shuffle[1]
+(page-shuffle) shuffle 2: cksum=$shuffle[2]
+(page-shuffle) shuffle 3: cksum=$shuffle[3]
+(page-shuffle) shuffle 4: cksum=$shuffle[4]
+(page-shuffle) shuffle 5: cksum=$shuffle[5]
+(page-shuffle) shuffle 6: cksum=$shuffle[6]
+(page-shuffle) shuffle 7: cksum=$shuffle[7]
+(page-shuffle) shuffle 8: cksum=$shuffle[8]
+(page-shuffle) shuffle 9: cksum=$shuffle[9]
+(page-shuffle) end
+EOF
+pass;
diff --git a/src/tests/vm/parallel-merge.c b/src/tests/vm/parallel-merge.c
new file mode 100644
index 0000000..cc09bb1
--- /dev/null
+++ b/src/tests/vm/parallel-merge.c
@@ -0,0 +1,149 @@
+/* Generates about 1 MB of random data that is then divided into
+ 16 chunks. A separate subprocess sorts each chunk; the
+ subprocesses run in parallel. Then we merge the chunks and
+ verify that the result is what it should be. */
+
+#include "tests/vm/parallel-merge.h"
+#include <stdio.h>
+#include <syscall.h>
+#include "tests/arc4.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#define CHUNK_SIZE (128 * 1024)
+#define CHUNK_CNT 8 /* Number of chunks. */
+#define DATA_SIZE (CHUNK_CNT * CHUNK_SIZE) /* Buffer size. */
+
+unsigned char buf1[DATA_SIZE], buf2[DATA_SIZE];
+size_t histogram[256];
+
+/* Initialize buf1 with random data,
+ then count the number of instances of each value within it. */
+static void
+init (void)
+{
+ struct arc4 arc4;
+ size_t i;
+
+ msg ("init");
+
+ arc4_init (&arc4, "foobar", 6);
+ arc4_crypt (&arc4, buf1, sizeof buf1);
+ for (i = 0; i < sizeof buf1; i++)
+ histogram[buf1[i]]++;
+}
+
+/* Sort each chunk of buf1 using SUBPROCESS,
+ which is expected to return EXIT_STATUS. */
+static void
+sort_chunks (const char *subprocess, int exit_status)
+{
+ pid_t children[CHUNK_CNT];
+ size_t i;
+
+ for (i = 0; i < CHUNK_CNT; i++)
+ {
+ char fn[128];
+ char cmd[128];
+ int handle;
+
+ msg ("sort chunk %zu", i);
+
+ /* Write this chunk to a file. */
+ snprintf (fn, sizeof fn, "buf%zu", i);
+ create (fn, CHUNK_SIZE);
+ quiet = true;
+ CHECK ((handle = open (fn)) > 1, "open \"%s\"", fn);
+ write (handle, buf1 + CHUNK_SIZE * i, CHUNK_SIZE);
+ close (handle);
+
+ /* Sort with subprocess. */
+ snprintf (cmd, sizeof cmd, "%s %s", subprocess, fn);
+ CHECK ((children[i] = exec (cmd)) != -1, "exec \"%s\"", cmd);
+ quiet = false;
+ }
+
+ for (i = 0; i < CHUNK_CNT; i++)
+ {
+ char fn[128];
+ int handle;
+
+ CHECK (wait (children[i]) == exit_status, "wait for child %zu", i);
+
+ /* Read chunk back from file. */
+ quiet = true;
+ snprintf (fn, sizeof fn, "buf%zu", i);
+ CHECK ((handle = open (fn)) > 1, "open \"%s\"", fn);
+ read (handle, buf1 + CHUNK_SIZE * i, CHUNK_SIZE);
+ close (handle);
+ quiet = false;
+ }
+}
+
+/* Merge the sorted chunks in buf1 into a fully sorted buf2. */
+static void
+merge (void)
+{
+ unsigned char *mp[CHUNK_CNT];
+ size_t mp_left;
+ unsigned char *op;
+ size_t i;
+
+ msg ("merge");
+
+ /* Initialize merge pointers. */
+ mp_left = CHUNK_CNT;
+ for (i = 0; i < CHUNK_CNT; i++)
+ mp[i] = buf1 + CHUNK_SIZE * i;
+
+ /* Merge. */
+ op = buf2;
+ while (mp_left > 0)
+ {
+ /* Find smallest value. */
+ size_t min = 0;
+ for (i = 1; i < mp_left; i++)
+ if (*mp[i] < *mp[min])
+ min = i;
+
+ /* Append value to buf2. */
+ *op++ = *mp[min];
+
+ /* Advance merge pointer.
+ Delete this chunk from the set if it's emptied. */
+ if ((++mp[min] - buf1) % CHUNK_SIZE == 0)
+ mp[min] = mp[--mp_left];
+ }
+}
+
+static void
+verify (void)
+{
+ size_t buf_idx;
+ size_t hist_idx;
+
+ msg ("verify");
+
+ buf_idx = 0;
+ for (hist_idx = 0; hist_idx < sizeof histogram / sizeof *histogram;
+ hist_idx++)
+ {
+ while (histogram[hist_idx]-- > 0)
+ {
+ if (buf2[buf_idx] != hist_idx)
+ fail ("bad value %d in byte %zu", buf2[buf_idx], buf_idx);
+ buf_idx++;
+ }
+ }
+
+ msg ("success, buf_idx=%'zu", buf_idx);
+}
+
+void
+parallel_merge (const char *child_name, int exit_status)
+{
+ init ();
+ sort_chunks (child_name, exit_status);
+ merge ();
+ verify ();
+}
diff --git a/src/tests/vm/parallel-merge.h b/src/tests/vm/parallel-merge.h
new file mode 100644
index 0000000..a6b6431
--- /dev/null
+++ b/src/tests/vm/parallel-merge.h
@@ -0,0 +1,6 @@
+#ifndef TESTS_VM_PARALLEL_MERGE
+#define TESTS_VM_PARALLEL_MERGE 1
+
+void parallel_merge (const char *child_name, int exit_status);
+
+#endif /* tests/vm/parallel-merge.h */
diff --git a/src/tests/vm/process_death.pm b/src/tests/vm/process_death.pm
new file mode 100644
index 0000000..52039a1
--- /dev/null
+++ b/src/tests/vm/process_death.pm
@@ -0,0 +1,22 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+
+sub check_process_death {
+ my ($proc_name) = @_;
+ our ($test);
+ my (@output) = read_text_file ("$test.output");
+
+ common_checks ("run", @output);
+ @output = get_core_output ("run", @output);
+ fail "First line of output is not `($proc_name) begin' message.\n"
+ if $output[0] ne "($proc_name) begin";
+ fail "Output missing '$proc_name: exit(-1)' message.\n"
+ if !grep ("$proc_name: exit(-1)" eq $_, @output);
+ fail "Output contains '($proc_name) end' message.\n"
+ if grep (/\($proc_name\) end/, @output);
+ pass;
+}
+
+1;
diff --git a/src/tests/vm/pt-bad-addr.c b/src/tests/vm/pt-bad-addr.c
new file mode 100644
index 0000000..3ca4084
--- /dev/null
+++ b/src/tests/vm/pt-bad-addr.c
@@ -0,0 +1,11 @@
+/* Accesses a bad address.
+ The process must be terminated with -1 exit code. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ fail ("bad addr read as %d", *(int *) 0x04000000);
+}
diff --git a/src/tests/vm/pt-bad-addr.ck b/src/tests/vm/pt-bad-addr.ck
new file mode 100644
index 0000000..09ea039
--- /dev/null
+++ b/src/tests/vm/pt-bad-addr.ck
@@ -0,0 +1,7 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::vm::process_death;
+
+check_process_death ('pt-bad-addr');
diff --git a/src/tests/vm/pt-bad-read.c b/src/tests/vm/pt-bad-read.c
new file mode 100644
index 0000000..ee791ff
--- /dev/null
+++ b/src/tests/vm/pt-bad-read.c
@@ -0,0 +1,16 @@
+/* Reads from a file into a bad address.
+ 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 *) &handle - 4096, 1);
+ fail ("survived reading data into bad address");
+}
diff --git a/src/tests/vm/pt-bad-read.ck b/src/tests/vm/pt-bad-read.ck
new file mode 100644
index 0000000..1f96bb4
--- /dev/null
+++ b/src/tests/vm/pt-bad-read.ck
@@ -0,0 +1,10 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(pt-bad-read) begin
+(pt-bad-read) open "sample.txt"
+pt-bad-read: exit(-1)
+EOF
+pass;
diff --git a/src/tests/vm/pt-big-stk-obj.c b/src/tests/vm/pt-big-stk-obj.c
new file mode 100644
index 0000000..6b630ec
--- /dev/null
+++ b/src/tests/vm/pt-big-stk-obj.c
@@ -0,0 +1,20 @@
+/* Allocates and writes to a 64 kB object on the stack.
+ This must succeed. */
+
+#include <string.h>
+#include "tests/arc4.h"
+#include "tests/cksum.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ char stk_obj[65536];
+ struct arc4 arc4;
+
+ arc4_init (&arc4, "foobar", 6);
+ memset (stk_obj, 0, sizeof stk_obj);
+ arc4_crypt (&arc4, stk_obj, sizeof stk_obj);
+ msg ("cksum: %lu", cksum (stk_obj, sizeof stk_obj));
+}
diff --git a/src/tests/vm/pt-big-stk-obj.ck b/src/tests/vm/pt-big-stk-obj.ck
new file mode 100644
index 0000000..eb5853a
--- /dev/null
+++ b/src/tests/vm/pt-big-stk-obj.ck
@@ -0,0 +1,10 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(pt-big-stk-obj) begin
+(pt-big-stk-obj) cksum: 3256410166
+(pt-big-stk-obj) end
+EOF
+pass;
diff --git a/src/tests/vm/pt-grow-bad.c b/src/tests/vm/pt-grow-bad.c
new file mode 100644
index 0000000..d4beba2
--- /dev/null
+++ b/src/tests/vm/pt-grow-bad.c
@@ -0,0 +1,14 @@
+/* Read from an address 4,096 bytes below the stack pointer.
+ The process must be terminated with -1 exit code. */
+
+#include <string.h>
+#include "tests/arc4.h"
+#include "tests/cksum.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ asm volatile ("movl -4096(%esp), %eax");
+}
diff --git a/src/tests/vm/pt-grow-bad.ck b/src/tests/vm/pt-grow-bad.ck
new file mode 100644
index 0000000..4c0ab8a
--- /dev/null
+++ b/src/tests/vm/pt-grow-bad.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']);
+(pt-grow-bad) begin
+pt-grow-bad: exit(-1)
+EOF
+pass;
diff --git a/src/tests/vm/pt-grow-pusha.c b/src/tests/vm/pt-grow-pusha.c
new file mode 100644
index 0000000..f9762a5
--- /dev/null
+++ b/src/tests/vm/pt-grow-pusha.c
@@ -0,0 +1,20 @@
+/* Expand the stack by 32 bytes all at once using the PUSHA
+ instruction.
+ This must succeed. */
+
+#include <string.h>
+#include "tests/arc4.h"
+#include "tests/cksum.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ asm volatile
+ ("movl %%esp, %%eax;" /* Save a copy of the stack pointer. */
+ "andl $0xfffff000, %%esp;" /* Move stack pointer to bottom of page. */
+ "pushal;" /* Push 32 bytes on stack at once. */
+ "movl %%eax, %%esp" /* Restore copied stack pointer. */
+ : : : "eax"); /* Tell GCC we destroyed eax. */
+}
diff --git a/src/tests/vm/pt-grow-pusha.ck b/src/tests/vm/pt-grow-pusha.ck
new file mode 100644
index 0000000..5000966
--- /dev/null
+++ b/src/tests/vm/pt-grow-pusha.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(pt-grow-pusha) begin
+(pt-grow-pusha) end
+EOF
+pass;
diff --git a/src/tests/vm/pt-grow-stack.c b/src/tests/vm/pt-grow-stack.c
new file mode 100644
index 0000000..0997a00
--- /dev/null
+++ b/src/tests/vm/pt-grow-stack.c
@@ -0,0 +1,20 @@
+/* Demonstrate that the stack can grow.
+ This must succeed. */
+
+#include <string.h>
+#include "tests/arc4.h"
+#include "tests/cksum.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ char stack_obj[4096];
+ struct arc4 arc4;
+
+ arc4_init (&arc4, "foobar", 6);
+ memset (stack_obj, 0, sizeof stack_obj);
+ arc4_crypt (&arc4, stack_obj, sizeof stack_obj);
+ msg ("cksum: %lu", cksum (stack_obj, sizeof stack_obj));
+}
diff --git a/src/tests/vm/pt-grow-stack.ck b/src/tests/vm/pt-grow-stack.ck
new file mode 100644
index 0000000..1e669db
--- /dev/null
+++ b/src/tests/vm/pt-grow-stack.ck
@@ -0,0 +1,10 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(pt-grow-stack) begin
+(pt-grow-stack) cksum: 3424492700
+(pt-grow-stack) end
+EOF
+pass;
diff --git a/src/tests/vm/pt-grow-stk-sc.c b/src/tests/vm/pt-grow-stk-sc.c
new file mode 100644
index 0000000..3efbb5f
--- /dev/null
+++ b/src/tests/vm/pt-grow-stk-sc.c
@@ -0,0 +1,32 @@
+/* This test checks that the stack is properly extended even if
+ the first access to a stack location occurs inside a system
+ call.
+
+ From Godmar Back. */
+
+#include <string.h>
+#include <syscall.h>
+#include "tests/vm/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ int handle;
+ int slen = strlen (sample);
+ char buf2[65536];
+
+ /* Write file via write(). */
+ CHECK (create ("sample.txt", slen), "create \"sample.txt\"");
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ CHECK (write (handle, sample, slen) == slen, "write \"sample.txt\"");
+ close (handle);
+
+ /* Read back via read(). */
+ CHECK ((handle = open ("sample.txt")) > 1, "2nd open \"sample.txt\"");
+ CHECK (read (handle, buf2 + 32768, slen) == slen, "read \"sample.txt\"");
+
+ CHECK (!memcmp (sample, buf2 + 32768, slen), "compare written data against read data");
+ close (handle);
+}
diff --git a/src/tests/vm/pt-grow-stk-sc.ck b/src/tests/vm/pt-grow-stk-sc.ck
new file mode 100644
index 0000000..23d3b02
--- /dev/null
+++ b/src/tests/vm/pt-grow-stk-sc.ck
@@ -0,0 +1,15 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(pt-grow-stk-sc) begin
+(pt-grow-stk-sc) create "sample.txt"
+(pt-grow-stk-sc) open "sample.txt"
+(pt-grow-stk-sc) write "sample.txt"
+(pt-grow-stk-sc) 2nd open "sample.txt"
+(pt-grow-stk-sc) read "sample.txt"
+(pt-grow-stk-sc) compare written data against read data
+(pt-grow-stk-sc) end
+EOF
+pass;
diff --git a/src/tests/vm/pt-write-code-2.c b/src/tests/vm/pt-write-code-2.c
new file mode 100644
index 0000000..83bcc2c
--- /dev/null
+++ b/src/tests/vm/pt-write-code-2.c
@@ -0,0 +1,15 @@
+/* Try to write to the code segment using a system call.
+ The process must be terminated with -1 exit code. */
+
+#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, (void *) test_main, 1);
+ fail ("survived reading data into code segment");
+}
diff --git a/src/tests/vm/pt-write-code.c b/src/tests/vm/pt-write-code.c
new file mode 100644
index 0000000..5072cec
--- /dev/null
+++ b/src/tests/vm/pt-write-code.c
@@ -0,0 +1,12 @@
+/* Try to write to the code segment.
+ The process must be terminated with -1 exit code. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ *(int *) test_main = 0;
+ fail ("writing the code segment succeeded");
+}
diff --git a/src/tests/vm/pt-write-code.ck b/src/tests/vm/pt-write-code.ck
new file mode 100644
index 0000000..65610fb
--- /dev/null
+++ b/src/tests/vm/pt-write-code.ck
@@ -0,0 +1,7 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::vm::process_death;
+
+check_process_death ('pt-write-code');
diff --git a/src/tests/vm/pt-write-code2.ck b/src/tests/vm/pt-write-code2.ck
new file mode 100644
index 0000000..69ffc77
--- /dev/null
+++ b/src/tests/vm/pt-write-code2.ck
@@ -0,0 +1,10 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(pt-write-code2) begin
+(pt-write-code2) open "sample.txt"
+pt-write-code2: exit(-1)
+EOF
+pass;
diff --git a/src/tests/vm/qsort.c b/src/tests/vm/qsort.c
new file mode 100644
index 0000000..922572c
--- /dev/null
+++ b/src/tests/vm/qsort.c
@@ -0,0 +1,136 @@
+#include "tests/vm/qsort.h"
+#include <stdbool.h>
+#include <debug.h>
+#include <random.h>
+
+/* Picks a pivot for the quicksort from the SIZE bytes in BUF. */
+static unsigned char
+pick_pivot (unsigned char *buf, size_t size)
+{
+ ASSERT (size >= 1);
+ return buf[random_ulong () % size];
+}
+
+/* Checks whether the SIZE bytes in ARRAY are divided into an
+ initial LEFT_SIZE elements all less than PIVOT followed by
+ SIZE - LEFT_SIZE elements all greater than or equal to
+ PIVOT. */
+static bool
+is_partitioned (const unsigned char *array, size_t size,
+ unsigned char pivot, size_t left_size)
+{
+ size_t i;
+
+ for (i = 0; i < left_size; i++)
+ if (array[i] >= pivot)
+ return false;
+
+ for (; i < size; i++)
+ if (array[i] < pivot)
+ return false;
+
+ return true;
+}
+
+/* Swaps the bytes at *A and *B. */
+static void
+swap (unsigned char *a, unsigned char *b)
+{
+ unsigned char t = *a;
+ *a = *b;
+ *b = t;
+}
+
+/* Partitions ARRAY in-place in an initial run of bytes all less
+ than PIVOT, followed by a run of bytes all greater than or
+ equal to PIVOT. Returns the length of the initial run. */
+static size_t
+partition (unsigned char *array, size_t size, int pivot)
+{
+ size_t left_size = size;
+ unsigned char *first = array;
+ unsigned char *last = first + left_size;
+
+ for (;;)
+ {
+ /* Move FIRST forward to point to first element greater than
+ PIVOT. */
+ for (;;)
+ {
+ if (first == last)
+ {
+ ASSERT (is_partitioned (array, size, pivot, left_size));
+ return left_size;
+ }
+ else if (*first >= pivot)
+ break;
+
+ first++;
+ }
+ left_size--;
+
+ /* Move LAST backward to point to last element no bigger
+ than PIVOT. */
+ for (;;)
+ {
+ last--;
+
+ if (first == last)
+ {
+ ASSERT (is_partitioned (array, size, pivot, left_size));
+ return left_size;
+ }
+ else if (*last < pivot)
+ break;
+ else
+ left_size--;
+ }
+
+ /* By swapping FIRST and LAST we extend the starting and
+ ending sequences that pass and fail, respectively,
+ PREDICATE. */
+ swap (first, last);
+ first++;
+ }
+}
+
+/* Returns true if the SIZE bytes in BUF are in nondecreasing
+ order, false otherwise. */
+static bool
+is_sorted (const unsigned char *buf, size_t size)
+{
+ size_t i;
+
+ for (i = 1; i < size; i++)
+ if (buf[i - 1] > buf[i])
+ return false;
+
+ return true;
+}
+
+/* Sorts the SIZE bytes in BUF into nondecreasing order, using
+ the quick-sort algorithm. */
+void
+qsort_bytes (unsigned char *buf, size_t size)
+{
+ if (!is_sorted (buf, size))
+ {
+ int pivot = pick_pivot (buf, size);
+
+ unsigned char *left_half = buf;
+ size_t left_size = partition (buf, size, pivot);
+ unsigned char *right_half = left_half + left_size;
+ size_t right_size = size - left_size;
+
+ if (left_size <= right_size)
+ {
+ qsort_bytes (left_half, left_size);
+ qsort_bytes (right_half, right_size);
+ }
+ else
+ {
+ qsort_bytes (right_half, right_size);
+ qsort_bytes (left_half, left_size);
+ }
+ }
+}
diff --git a/src/tests/vm/qsort.h b/src/tests/vm/qsort.h
new file mode 100644
index 0000000..61b65f3
--- /dev/null
+++ b/src/tests/vm/qsort.h
@@ -0,0 +1,8 @@
+#ifndef TESTS_VM_QSORT_H
+#define TESTS_VM_QSORT_H 1
+
+#include <stddef.h>
+
+void qsort_bytes (unsigned char *buf, size_t size);
+
+#endif /* tests/vm/qsort.h */
diff --git a/src/tests/vm/sample.inc b/src/tests/vm/sample.inc
new file mode 100644
index 0000000..a60a139
--- /dev/null
+++ b/src/tests/vm/sample.inc
@@ -0,0 +1,19 @@
+char sample[] = {
+ "=== ALL USERS PLEASE NOTE ========================\n"
+ "\n"
+ "CAR and CDR now return extra values.\n"
+ "\n"
+ "The function CAR now returns two values. Since it has to go to the\n"
+ "trouble to figure out if the object is carcdr-able anyway, we figured\n"
+ "you might as well get both halves at once. For example, the following\n"
+ "code shows how to destructure a cons (SOME-CONS) into its two slots\n"
+ "(THE-CAR and THE-CDR):\n"
+ "\n"
+ " (MULTIPLE-VALUE-BIND (THE-CAR THE-CDR) (CAR SOME-CONS) ...)\n"
+ "\n"
+ "For symmetry with CAR, CDR returns a second value which is the CAR of\n"
+ "the object. In a related change, the functions MAKE-ARRAY and CONS\n"
+ "have been fixed so they don't allocate any storage except on the\n"
+ "stack. This should hopefully help people who don't like using the\n"
+ "garbage collector because it cold boots the machine so often.\n"
+};
diff --git a/src/tests/vm/sample.txt b/src/tests/vm/sample.txt
new file mode 100644
index 0000000..c446830
--- /dev/null
+++ b/src/tests/vm/sample.txt
@@ -0,0 +1,17 @@
+=== ALL USERS PLEASE NOTE ========================
+
+CAR and CDR now return extra values.
+
+The function CAR now returns two values. Since it has to go to the
+trouble to figure out if the object is carcdr-able anyway, we figured
+you might as well get both halves at once. For example, the following
+code shows how to destructure a cons (SOME-CONS) into its two slots
+(THE-CAR and THE-CDR):
+
+ (MULTIPLE-VALUE-BIND (THE-CAR THE-CDR) (CAR SOME-CONS) ...)
+
+For symmetry with CAR, CDR returns a second value which is the CAR of
+the object. In a related change, the functions MAKE-ARRAY and CONS
+have been fixed so they don't allocate any storage except on the
+stack. This should hopefully help people who don't like using the
+garbage collector because it cold boots the machine so often.