lib9p

Go 9P library.
Log | Files | Refs | LICENSE

file_test.go (7346B)


      1 package client
      2 
      3 import (
      4 	"bytes"
      5 	"context"
      6 	"fmt"
      7 	"io"
      8 	"io/fs"
      9 	"reflect"
     10 	"strings"
     11 	"testing"
     12 
     13 	"git.mtkn.jp/lib9p"
     14 )
     15 
     16 func TestFileInterface(t *testing.T) {
     17 	var _ lib9p.File = &File{}
     18 	var _ lib9p.WriterFile = &File{}
     19 	var _ lib9p.WriterStatFile = &File{}
     20 	var _ lib9p.ReadDirFile = &File{}
     21 }
     22 
     23 // Mount runs a server with fs and mounts it as *FS.
     24 func mount(ctx context.Context, fs lib9p.FS) (cfs *FS, err error) {
     25 	cr, sw := io.Pipe()
     26 	sr, cw := io.Pipe()
     27 	s := lib9p.NewServer(fs)
     28 	go s.Serve(ctx, sr, sw)
     29 	cfs, err = Mount(ctx, cr, cw, "glenda", "")
     30 	if err != nil {
     31 		return nil, fmt.Errorf("mount: %v", err)
     32 	}
     33 	return cfs, nil
     34 }
     35 
     36 // TestFileStat tests whether Stat returns the same lib9p.Stat as testfs defines.
     37 func TestFileStat(t *testing.T) {
     38 	ctx, cancel := context.WithCancel(context.Background())
     39 	cfs, err := mount(ctx, testfs)
     40 	if err != nil {
     41 		t.Fatal(err)
     42 	}
     43 	defer cancel()
     44 	var walk = func(path string, d fs.DirEntry, err error) error {
     45 		cfi, err := fs.Stat(lib9p.ExportFS{FS: cfs}, path)
     46 		if err != nil {
     47 			if strings.Contains(err.Error(), "permission") {
     48 				return nil
     49 			}
     50 			t.Error(err)
     51 			return nil
     52 		}
     53 		tfi, err := fs.Stat(lib9p.ExportFS{FS: testfs}, path)
     54 		if err != nil {
     55 			t.Error(err)
     56 		}
     57 		if !reflect.DeepEqual(cfi, tfi) {
     58 			t.Errorf("fi not match:\n\twant: %v\n\tgot:  %v", tfi, cfi)
     59 		}
     60 		return nil
     61 	}
     62 	if err := fs.WalkDir(lib9p.ExportFS{FS: testfs}, ".", walk); err != nil {
     63 		t.Error(err)
     64 	}
     65 }
     66 
     67 // TODO: implement
     68 func TestFileWStat(t *testing.T) {
     69 	ctx, cancel := context.WithCancel(context.Background())
     70 	cfs, err := mount(ctx, testfs)
     71 	if err != nil {
     72 		t.Fatal(err)
     73 	}
     74 	defer cancel()
     75 	_, _ = ctx, cfs
     76 }
     77 
     78 // TestClose checks it *File.Close calls are transmitted to the server and
     79 // *testFile.Close is called for each *File.Close.
     80 func TestClose(t *testing.T) {
     81 	ctx, cancel := context.WithCancel(context.Background())
     82 	cfs, err := mount(ctx, testfs)
     83 	if err != nil {
     84 		t.Fatal(err)
     85 	}
     86 	defer cancel()
     87 	var walk = func(path string, d fs.DirEntry, err error) error {
     88 		if err != nil {
     89 			if err == io.EOF {
     90 				return nil
     91 			}
     92 			return err
     93 		}
     94 		tf, err := testfs.walkPath(path)
     95 		if err != nil {
     96 			t.Error(err)
     97 			return nil
     98 		}
     99 		cf, err := cfs.OpenFile(path, lib9p.O_RDONLY)
    100 		if err != nil {
    101 			if strings.Contains(err.Error(), "permission") {
    102 				return nil
    103 			}
    104 			return err
    105 		}
    106 		cc := make(chan struct{})
    107 		tf.closec = cc
    108 		cf.Close()
    109 		<-cc
    110 		return nil
    111 	}
    112 	if err := fs.WalkDir(lib9p.ExportFS{FS: testfs}, ".", walk); err != nil {
    113 		t.Error(err)
    114 	}
    115 }
    116 
    117 // TestFileRead checks *File.Read reads the same []byte as *testFile.content.
    118 func TestFileRead(t *testing.T) {
    119 	ctx, cancel := context.WithCancel(context.Background())
    120 	cfs, err := mount(ctx, testfs)
    121 	if err != nil {
    122 		t.Fatal(err)
    123 	}
    124 	defer cancel()
    125 	var walk = func(path string, d fs.DirEntry, err error) error {
    126 		if err != nil {
    127 			if err == io.EOF {
    128 				return nil
    129 			}
    130 			return err
    131 		}
    132 		if d.IsDir() {
    133 			return nil
    134 		}
    135 		tf, err := testfs.walkPath(path)
    136 		if err != nil {
    137 			t.Error(err)
    138 			return nil
    139 		}
    140 		cf, err := cfs.OpenFile(path, lib9p.O_RDONLY)
    141 		if err != nil {
    142 			if strings.Contains(err.Error(), "permission") {
    143 				return nil
    144 			}
    145 			return err
    146 		}
    147 		defer cf.Close()
    148 		cb := make([]byte, len(tf.content)*2)
    149 		n, err := cf.Read(cb)
    150 		if err != nil {
    151 			t.Error(err)
    152 			return nil
    153 		}
    154 		if n != len(tf.content) || !bytes.Equal(cb[:n], tf.content) {
    155 			t.Errorf("read wrong content: want: %v, got: %v", tf.content, cb[:n])
    156 			return nil
    157 		}
    158 		return nil
    159 	}
    160 	if err := fs.WalkDir(lib9p.ExportFS{FS: testfs}, ".", walk); err != nil {
    161 		t.Error(err)
    162 	}
    163 }
    164 
    165 // TestFileWrite tests the Write method of the struct File.
    166 // It writes some bytes to a testFile using File.Write and checks if
    167 // the underlying content of testFile is changed correctly.
    168 func TestFileWrite(t *testing.T) {
    169 	ctx, cancel := context.WithCancel(context.Background())
    170 	cfs, err := mount(ctx, testfs)
    171 	if err != nil {
    172 		t.Fatal(err)
    173 	}
    174 	defer cancel()
    175 	tf, err := testfs.walkPath("dir/file")
    176 	if err != nil {
    177 		t.Fatal(err)
    178 	}
    179 	orig := bytes.Clone(tf.content)
    180 	defer func() { tf.content = bytes.Clone(orig) }()
    181 	// consecutive writes to the same file without closing it.
    182 	ctnt0 := []byte("hogehoge")
    183 	ctnt1 := []byte("fugafuga")
    184 	cf, err := cfs.OpenFile("dir/file", lib9p.O_RDWR)
    185 	if err != nil {
    186 		t.Fatal(err)
    187 	}
    188 	_, err = cf.(lib9p.WriterFile).Write(ctnt0)
    189 	if err != nil {
    190 		t.Fatal(err)
    191 	}
    192 	_, err = cf.(lib9p.WriterFile).Write(ctnt1)
    193 	if err != nil {
    194 		t.Fatal(err)
    195 	}
    196 	if !bytes.Equal(tf.content[:len(ctnt0)+len(ctnt1)], bytes.Join([][]byte{ctnt0, ctnt1}, nil)) {
    197 		t.Errorf("not written propperly: want: %v, got: %v",
    198 			string(bytes.Join([][]byte{ctnt0, ctnt1}, nil)),
    199 			string(tf.content[:len(ctnt0)+len(ctnt1)]))
    200 	}
    201 }
    202 
    203 func FuzzFileWrite(f *testing.F) {
    204 	for _, seed := range []string{"", "fuga", "fugafuga", "fugafugafuga"} {
    205 		f.Add([]byte(seed))
    206 	}
    207 	f.Add(make([]byte, 9000))
    208 	ctx, cancel := context.WithCancel(context.Background())
    209 	cfs, err := mount(ctx, testfs)
    210 	if err != nil {
    211 		f.Fatal(err)
    212 	}
    213 	defer cancel()
    214 	tf, err := testfs.walkPath("dir/file")
    215 	if err != nil {
    216 		f.Fatal(err)
    217 	}
    218 	orig := bytes.Clone(tf.content)
    219 	f.Fuzz(func(t *testing.T, in []byte) {
    220 		defer func() { tf.content = bytes.Clone(orig) }()
    221 		cf, err := cfs.OpenFile("dir/file", lib9p.O_RDWR)
    222 		if err != nil {
    223 			t.Fatal(err)
    224 		}
    225 		_, err = cf.(lib9p.WriterFile).Write(in)
    226 		if err != nil {
    227 			t.Fatal(err)
    228 		}
    229 		if !bytes.Equal(tf.content[:len(in)], in) {
    230 			t.Errorf("not written propperly: want: %v, got: %v",
    231 				string(in), string(tf.content[:len(in)]))
    232 		}
    233 		cf.Close()
    234 	})
    235 }
    236 
    237 // TestReadDir tests whether ReadDir returns the same dir entries as testfs
    238 // by comparing their *lib9p.Stat.
    239 func TestReadDir(t *testing.T) {
    240 	ctx, cancel := context.WithCancel(context.Background())
    241 	cfs, err := mount(ctx, testfs)
    242 	if err != nil {
    243 		t.Fatal(err)
    244 	}
    245 	defer cancel()
    246 	var walk = func(path string, d fs.DirEntry, err error) error {
    247 		t.Log(path)
    248 		if err != nil {
    249 			return err
    250 		}
    251 		if !d.IsDir() {
    252 			return nil
    253 		}
    254 		cf, err := cfs.OpenFile(path, lib9p.O_RDONLY)
    255 		if err != nil {
    256 			if !strings.Contains(err.Error(), "permission") {
    257 				t.Error(err)
    258 			}
    259 			return nil
    260 		}
    261 		cde, err := cf.(lib9p.ReadDirFile).ReadDir(-1)
    262 		if err != nil {
    263 			if err != io.EOF {
    264 				t.Error(err)
    265 				return nil
    266 			}
    267 		}
    268 		tf, err := testfs.OpenFile(path, lib9p.O_RDONLY)
    269 		if err != nil {
    270 			if !strings.Contains(err.Error(), "permission") {
    271 				t.Error(err)
    272 			}
    273 			return nil
    274 		}
    275 		tde, err := tf.(lib9p.ReadDirFile).ReadDir(-1)
    276 		if err != nil {
    277 			if err != io.EOF {
    278 				t.Error(err)
    279 				return nil
    280 			}
    281 		}
    282 		if len(cde) != len(tde) {
    283 			t.Errorf("%s: length of direntry not match", path)
    284 			return nil
    285 		}
    286 		demap := make(map[string]*lib9p.Stat)
    287 		for _, cde0 := range cde {
    288 			info, err := cde0.Info()
    289 			if err != nil {
    290 				t.Error(err)
    291 				return nil
    292 			}
    293 			demap[cde0.Name()] = info.Sys().(*lib9p.Stat)
    294 		}
    295 		for _, tde0 := range tde {
    296 			info, err := tde0.Info()
    297 			if err != nil {
    298 				t.Error(err)
    299 				return nil
    300 			}
    301 			if !reflect.DeepEqual(demap[tde0.Name()], info.Sys().(*lib9p.Stat)) {
    302 				t.Errorf("%s: stat doesn't match:\n\twant: %v\n\tgot:  %v",
    303 					path, info.Sys().(*lib9p.Stat), demap[tde0.Name()])
    304 				return nil
    305 			}
    306 		}
    307 		return nil
    308 	}
    309 	if err := fs.WalkDir(lib9p.ExportFS{FS: testfs}, ".", walk); err != nil {
    310 		t.Error(err)
    311 	}
    312 }