// Create a new file on the file system, ensure that it is given the // appropriate inode number and created successfully. Then unlink the file // so the file system remains in the same state it began in. func TestCreateThenUnlink(test *testing.T) { fs, proc := OpenMinixImage(test) alloc := proc.rootdir.Devinfo.AllocTbl // Check the state of the bitmap before creating this file inum, err := alloc.AllocInode() if err != nil { testutils.FatalHere(test, "Error pre-allocating an inode: %s", err) } alloc.FreeInode(inum) // Get block 13 and print it before we make any changes //bp := fs.bcache.GetBlock(ROOT_DEVICE, 13, INODE_BLOCK, NORMAL) //debug.PrintBlock(bp, fs.devinfo[ROOT_DEVICE]) // Create a new file and check allocation tables, etc. file, err := fs.Open(proc, "/tmp/created_file", common.O_CREAT, 0666) if err != nil { testutils.FatalHere(test, "Failed when creating new file: %s", err) } filp := file.(*filp) if filp.inode.Inum != inum { testutils.ErrorHere(test, "Inum mismatch expected %d, got %d", inum, filp.inode.Inum) } // Close and unlink the new file err = fs.Close(proc, file) if err != nil { testutils.ErrorHere(test, "Failed when closing new file: %s", err) } err = fs.Unlink(proc, "/tmp/created_file") if err != nil { testutils.ErrorHere(test, "Failed when unlinking new file: %s", err) } // The bit we just freed should be our next inum2, err := alloc.AllocInode() if err != nil { testutils.FatalHere(test, "Failed when checking inode allocation: %s", err) } if inum != inum2 { testutils.FatalHere(test, "Inode mismatch expected %d, got %d", inum, inum2) } alloc.FreeInode(inum2) fs.Exit(proc) err = fs.Shutdown() if err != nil { testutils.FatalHere(test, "Failed when shutting down filesystem: %s", err) } }
// Test to ensure that blocks are re-used in last-recently-used order, i.e. // in the reverse order they are 'put' back into the cache. func TestLRUOrder(test *testing.T) { dev, cache := openTestCache(test) // get 10 blocks blocks := make([]*common.CacheBlock, 10) for i := 0; i < 10; i++ { blocks[i] = cache.GetBlock(0, i, common.FULL_DATA_BLOCK, common.NORMAL) } // put them back for i := 0; i < 10; i++ { cache.PutBlock(blocks[i], common.FULL_DATA_BLOCK) } // now fetch 10 more different blocks blocks2 := make([]*common.CacheBlock, 10) for i := 0; i < 10; i++ { blocks2[i] = cache.GetBlock(0, i+10, common.FULL_DATA_BLOCK, common.NORMAL) if blocks2[i] != blocks[0+i] { testutils.ErrorHere(test, "cache block mismatch, expected %p, got %p", blocks[9-i], blocks2[i]) } } closeTestCache(test, dev, cache) }
func openTestCache(test *testing.T) (common.BlockDevice, *LRUCache) { dev := testutils.NewTestDevice(test, 64, 100) cache := NewLRUCache(4, 10, 16) err := cache.MountDevice(0, dev, getDevInfo(64)) if err != nil { testutils.ErrorHere(test, "Failed when mounting ramdisk device into cache: %s", err) } return dev, cache.(*LRUCache) }
func closeTestCache(test *testing.T, dev common.BlockDevice, cache *LRUCache) { cache.Flush(0) err := cache.UnmountDevice(0) if err != nil { testutils.ErrorHere(test, "Failed when unmounting ramdisk device: %s", err) } // if err = cache.Close(); err != nil { // ErrorHere(test, "Failed when closing cache: %s", err) // } // if err = dev.Close(); err != nil { // ErrorHere(test, "Failed when closing device: %s", err) // } }
func TestGetConcurrency(test *testing.T) { dev, cache := openTestCache(test) bdev := testutils.NewBlockingDevice(testutils.NewTestDevice(test, 64, 100)) cache.MountDevice(1, bdev, getDevInfo(64)) // Test that reads from a normal device are not blocked by reads from a // broken device. wg := new(sync.WaitGroup) wg.Add(2) go func() { // Do the read on the broken device cb := cache.GetBlock(1, 0, common.FULL_DATA_BLOCK, common.NORMAL) cache.PutBlock(cb, common.FULL_DATA_BLOCK) wg.Done() }() go func() { // Wait for the device to be blocked <-bdev.HasBlocked cb := cache.GetBlock(0, 0, common.FULL_DATA_BLOCK, common.NORMAL) // Now unblock that device so we can shut down bdev.Unblock <- true cache.PutBlock(cb, common.FULL_DATA_BLOCK) wg.Done() }() wg.Wait() if err := cache.UnmountDevice(1); err != nil { testutils.ErrorHere(test, "Failed when unmounting device: %s", err) } if err := bdev.Close(); err != nil { testutils.ErrorHere(test, "Failed when closing device: %s", err) } closeTestCache(test, dev, cache) }
func TestCacheFullPanic(test *testing.T) { dev, cache := openTestCache(test) for i := 0; i < 10; i++ { _ = cache.GetBlock(0, i, common.FULL_DATA_BLOCK, common.NORMAL) } done := make(chan bool) go func() { defer func() { if x := recover(); x == nil { testutils.ErrorHere(test, "Expected all buffers in use panic") } done <- true }() _ = cache.GetBlock(0, 11, common.FULL_DATA_BLOCK, common.NORMAL) }() <-done closeTestCache(test, dev, cache) }
// Test that blocks are cached. This test will deadlock if more than one block // read is attempted from the underlying device. func TestDoesCache(test *testing.T) { // Open an always-broken device dev := testutils.NewBlockingDevice(testutils.NewTestDevice(test, 64, 100)) cache := NewLRUCache(4, 10, 16) err := cache.MountDevice(0, dev, getDevInfo(64)) if err != nil { testutils.ErrorHere(test, "Failed when mounting ramdisk device into cache: %s", err) } wg := new(sync.WaitGroup) wg.Add(2) go func() { // Allow a single block to be read <-dev.HasBlocked dev.Unblock <- true wg.Done() }() go func() { cb1 := cache.GetBlock(0, 5, common.FULL_DATA_BLOCK, common.NORMAL) data, ok := cb1.Block.(common.FullDataBlock) if !ok { testutils.ErrorHere(test, "Did not get a FullDataBlock") } if data[0] != 5 { testutils.ErrorHere(test, "Data in block did not match, expected %x, got %x", 5, data[0]) } // this should be pulled from the cache, not from the device cb2 := cache.GetBlock(0, 5, common.FULL_DATA_BLOCK, common.NORMAL) if cb1 != cb2 { testutils.ErrorHere(test, "Cache block mismatch, expected %p, got %p", cb1, cb2) } data, ok = cb2.Block.(common.FullDataBlock) if !ok { testutils.ErrorHere(test, "Did not get a FullDataBlock") } if data[0] != 5 { testutils.ErrorHere(test, "Data in block did not match, expected %x, got %x", 5, data[0]) } cache.PutBlock(cb1, common.FULL_DATA_BLOCK) cache.PutBlock(cb2, common.FULL_DATA_BLOCK) wg.Done() }() wg.Wait() err = cache.UnmountDevice(0) if err != nil { testutils.ErrorHere(test, "Failed when unmounting ramdisk device: %s", err) } // if err = cache.Close(); err != nil { // ErrorHere(test, "Failed when closing cache: %s", err) // } // if err = dev.Close(); err != nil { // ErrorHere(test, "Failed when closing device: %s", err) // } }
// Make a new directory on the file system, ensure that it is given the // appropriate number/contents, then rmdir the file and check that the file // system is returned to its initial state. func TestMkdir(test *testing.T) { fs, proc := OpenMinixImage(test) bitmap := proc.rootdir.Devinfo.AllocTbl // Check the state of the bitmaps before creating this file inum, err := bitmap.AllocInode() if err != nil { testutils.FatalHere(test, "Error pre-allocating an inode: %s", err) } bitmap.FreeInode(inum) znum, err := bitmap.AllocZone(common.NO_ZONE) if err != nil { testutils.FatalHere(test, "Error pre-allocating a zone: %s", err) } bitmap.FreeZone(znum) // Create a new file and check allocation tables, etc. err = fs.Mkdir(proc, "/tmp/new_directory", 0666) if err != nil { testutils.FatalHere(test, "Failed when creating new directory: %s", err) } dirp, err := fs.eatPath(proc, "/tmp/new_directory") if err != nil { testutils.FatalHere(test, "Failed when looking up new directory: %s", err) } if dirp.Inum != inum { testutils.ErrorHere(test, "Inum mismatch expected %d, got %d", inum, dirp.Inum) } ok, devnum, inum := Lookup(dirp, ".") if !ok { testutils.ErrorHere(test, "Current directory . lookup failed") } if devnum != dirp.Devinfo.Devnum { testutils.ErrorHere(test, "Current directory . devnum mismatch expected %d, got %d", dirp.Devinfo.Devnum, devnum) } if inum != dirp.Inum { testutils.ErrorHere(test, "Current directory . inum mismatch expected %d, got %d", dirp.Inum, inum) } if !dirp.IsDirectory() { testutils.ErrorHere(test, "New directory is not a directory") } if dirp.Nlinks != 2 { testutils.ErrorHere(test, "Links mismatch expected %d, got %d", 2, dirp.Nlinks) } if dirp.Size != 128 { testutils.ErrorHere(test, "Directory size mismatch expected %d, got %d", 128, dirp.Size) } fs.itable.PutInode(dirp) // Remove the new directory err = fs.Rmdir(proc, "/tmp/new_directory") if err != nil { testutils.ErrorHere(test, "Failed when unlinking new directory: %s", err) } // The bit we just freed should be our next inum2, err := bitmap.AllocInode() if err != nil { testutils.ErrorHere(test, "Failed when checking inode allocation: %s", err) } if inum != inum2 { testutils.ErrorHere(test, "Inode mismatch expected %d, got %d", inum, inum2) } bitmap.FreeInode(inum2) fs.Exit(proc) err = fs.Shutdown() if err != nil { testutils.FatalHere(test, "Failed when shutting down filesystem: %s", err) } }
// Test write functionality by taking the data from the host system and // writing it to the guest system along with the host system (at the same // time) and comparing the returns from these functions. Then compare the // written data to the original data to make sure it was written correctly. func TestWrite(test *testing.T) { fs, proc := OpenMinixImage(test) ofile := OpenEuroparl(test) // Read the data for the entire file filesize := 4489799 // known filedata, err := ioutil.ReadAll(ofile) if err != nil { testutils.FatalHere(test, "Failed when reading from original file: %s", err) } if filesize != len(filedata) { testutils.FatalHere(test, "File content sizes differ: %v != %v", len(filedata), filesize) } if ofile.Close() != nil { testutils.FatalHere(test, "Failed when closing original file: %s", err) } // Open the two files that will be written to gfile, err := fs.Open(proc, "/tmp/europarl-en.txt", common.O_CREAT|common.O_TRUNC|common.O_RDWR, 0666) if err != nil || gfile == nil { testutils.FatalHere(test, "Could not open file on guest: %s", err) } hfile, err := os.OpenFile("/tmp/europarl-en.txt", os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0666) if err != nil || hfile == nil { testutils.FatalHere(test, "Could not open file on host: %s", err) } // Write the data to a file on the host/guest operating systems in sync blocksize := fs.devinfo[common.ROOT_DEVICE].Blocksize numbytes := blocksize + (blocksize / 3) pos := 0 for pos < filesize { // Write the next numbytes bytes to the file endpos := pos + numbytes if endpos > filesize { endpos = filesize } data := filedata[pos:endpos] gn, gerr := gfile.Write(data) hn, herr := hfile.Write(data) if gn != hn { testutils.ErrorHere(test, "Bytes read mismatch at offset %d: expected %d, got %d", pos, hn, gn) } if gerr != herr { testutils.ErrorHere(test, "Error mismatch at offset %d: expected '%s', got '%s'", pos, herr, gerr) } rip, err := fs.eatPath(proc, "/tmp/europarl-en.txt") if err != nil { testutils.FatalHere(test, "After write at position %d: could not locate newly created file: %s", pos, err) } else { fs.itable.PutInode(rip) } pos += gn } if hfile.Close() != nil { testutils.ErrorHere(test, "Failed when closing host file") } // Seek to beginning of file gfile.Seek(0, 0) written := make([]byte, filesize) n, err := gfile.Read(written) if n != filesize { testutils.ErrorHere(test, "Verify count mismatch expected %d, got %d", filesize, n) } if err != nil { testutils.ErrorHere(test, "Error when reading to verify: %s", err) } compare := bytes.Compare(filedata, written) if compare != 0 { testutils.ErrorHere(test, "Error comparing written data expected %d, got %d", 0, compare) } if fs.Close(proc, gfile) != nil { testutils.ErrorHere(test, "Error when closing out the written file: %s", err) } err = fs.Unlink(proc, "/tmp/europarl-en.txt") if err != nil { testutils.ErrorHere(test, "Failed when unlinking written file: %s", err) } fs.Exit(proc) err = fs.Shutdown() if err != nil { testutils.FatalHere(test, "Failed when shutting down filesystem: %s", err) } }
// Test that path lookups function properly func TestEatPath(test *testing.T) { fs, proc := OpenMinixImage(test) // Fetch some additional inodes to ensure path lookup is functioning // properly. // // inode permission link size name // 1 drwxr-xr-x 14 1088 / // 2 drwxr-xr-x 2 128 /usr // 541 drwxr-xr-x 2 192 /sample // 542 -rw-r--r-- 1 4489799 /sample/europarl-en.txt // 35 -rw------- 2 2705920 /boot/image/3.1.8 // 540 -rw-r--r-- 1 395 /root/.ssh/known_hosts // 481 -rw------- 1 5 /var/run/syslogd.pid type inodeTest struct { path string inum int links int size int zones []int } inodeTests := []inodeTest{ {"/", 1, 14, 1088, nil}, {"/usr", 2, 2, 128, nil}, {"/sample", 541, 2, 192, nil}, {"/sample/europarl-en.txt", 542, 1, 4489799, nil}, {"/boot/image/3.1.8", 35, 2, 2705920, nil}, {"/root/.ssh/known_hosts", 540, 1, 395, nil}, {"/var/run/syslogd.pid", 481, 1, 5, nil}, } for _, itest := range inodeTests { rip, err := fs.eatPath(proc, itest.path) if err != nil { testutils.FatalHere(test, "Failed when fetching inode for %s: %s", itest.path, err) } if itest.inum != -1 && rip.Inum != itest.inum { testutils.ErrorHere(test, "[%s] mismatch for inum got %d, expected %d", itest.path, rip.Inum, itest.inum) } if itest.links != -1 && rip.Nlinks != uint16(itest.links) { testutils.ErrorHere(test, "[%s] mismatch for links got %d, expected %d", itest.path, rip.Nlinks, itest.links) } if itest.size != -1 && rip.Size != int32(itest.size) { testutils.ErrorHere(test, "[%s] mismatch for size got %d, expected %d", itest.path, rip.Size, itest.size) } for i := 0; i < 10; i++ { if i < len(itest.zones) && rip.Zone[i] != uint32(itest.zones[i]) { testutils.ErrorHere(test, "[%s] mismatch for zone[%d] got %d, expected %d", i, itest.path, rip.Zone[i], itest.zones[i]) } } // Convert the test to use a relative path and then compare the inodes if len(itest.path) > 1 { relpath := itest.path[1:] relrip, err := fs.eatPath(proc, relpath) if err != nil { testutils.ErrorHere(test, "Failed fetching relative path %s", relpath) } if relrip != rip { testutils.ErrorHere(test, "Relative inode does not match absolute inode for path %s", itest.path) } fs.itable.PutInode(relrip) } fs.itable.PutInode(rip) } fs.Exit(proc) err := fs.Shutdown() if err != nil { testutils.FatalHere(test, "Failed when shutting down filesystem: %s", err) } }