go-hep.org/x/hep@v0.38.1/xrootd/filesystem_test.go (about)

     1  // Copyright ©2018 The go-hep Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package xrootd
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"log"
    11  	"os"
    12  	"path"
    13  	"path/filepath"
    14  	"reflect"
    15  	"testing"
    16  
    17  	"go-hep.org/x/hep/xrootd/xrdfs"
    18  )
    19  
    20  var fstest = map[string]*xrdfs.EntryStat{
    21  	"/tmp/dir1/file1.txt": {
    22  		HasStatInfo: true,
    23  		ID:          139698106334466,
    24  		EntrySize:   0,
    25  		Mtime:       1530559859,
    26  		Flags:       xrdfs.StatIsReadable,
    27  	},
    28  }
    29  
    30  func tempdir(client *Client, dir, prefix string) (name string, err error) {
    31  	name, err = os.MkdirTemp("", prefix)
    32  	if err != nil {
    33  		return "", err
    34  	}
    35  	os.RemoveAll(name)
    36  
    37  	// Cross-platform way of obtaining the directory name.
    38  	name = filepath.ToSlash(name)
    39  	name = path.Base(name)
    40  
    41  	name = path.Join(dir, name)
    42  
    43  	fs := client.FS()
    44  	err = fs.MkdirAll(context.Background(), name, xrdfs.OpenModeOwnerRead|xrdfs.OpenModeOwnerWrite|xrdfs.OpenModeOwnerExecute)
    45  	if err != nil {
    46  		return "", fmt.Errorf("could not create tempdir: %w", err)
    47  	}
    48  	return name, nil
    49  }
    50  
    51  func testFileSystem_Dirlist(t *testing.T, addr string) {
    52  	var want = []xrdfs.EntryStat{
    53  		*fstest["/tmp/dir1/file1.txt"],
    54  	}
    55  	want[0].EntryName = "file1.txt"
    56  
    57  	client, err := NewClient(context.Background(), addr, "gopher")
    58  	if err != nil {
    59  		t.Fatalf("could not create client: %v", err)
    60  	}
    61  	defer client.Close()
    62  
    63  	fs := client.FS()
    64  
    65  	got, err := fs.Dirlist(context.Background(), "/tmp/dir1")
    66  	if err != nil {
    67  		t.Fatalf("invalid protocol call: %v", err)
    68  	}
    69  	if !reflect.DeepEqual(got, want) {
    70  		t.Errorf("FileSystem.Dirlist()\ngot = %v\nwant = %v", got, want)
    71  	}
    72  }
    73  
    74  func TestFileSystem_Dirlist(t *testing.T) {
    75  	for i := range testClientAddrs {
    76  		addr := testClientAddrs[i]
    77  		t.Run(addr, func(t *testing.T) {
    78  			t.Parallel()
    79  
    80  			testFileSystem_Dirlist(t, addr)
    81  		})
    82  	}
    83  }
    84  
    85  func testFileSystem_Open(t *testing.T, addr string, options xrdfs.OpenOptions, wantFileHandle xrdfs.FileHandle, wantFileCompression *xrdfs.FileCompression, wantFileInfo *xrdfs.EntryStat) {
    86  	client, err := NewClient(context.Background(), addr, "gopher")
    87  	if err != nil {
    88  		t.Fatalf("could not create client: %v", err)
    89  	}
    90  	defer client.Close()
    91  
    92  	fs := client.FS()
    93  
    94  	gotFile, err := fs.Open(context.Background(), "/tmp/dir1/file1.txt", xrdfs.OpenModeOtherRead, options)
    95  	if err != nil {
    96  		t.Fatalf("invalid open call: %v", err)
    97  	}
    98  	defer gotFile.Close(context.Background())
    99  
   100  	if !reflect.DeepEqual(gotFile.Handle(), wantFileHandle) {
   101  		t.Errorf("FileSystem.Open()\ngotFile.Handle() = %v\nwantFileHandle = %v", gotFile.Handle(), wantFileHandle)
   102  	}
   103  
   104  	if !reflect.DeepEqual(gotFile.Compression(), wantFileCompression) {
   105  		// TODO: Remove this workaround when fix for https://github.com/xrootd/xrootd/issues/721 will be released.
   106  		skippedDefaultCompressionValue := reflect.DeepEqual(wantFileCompression, &xrdfs.FileCompression{}) && gotFile.Compression() == nil
   107  		if !skippedDefaultCompressionValue {
   108  			t.Errorf("FileSystem.Open()\ngotFile.Compression() = %v\nwantFileCompression = %v", gotFile.Compression(), wantFileCompression)
   109  		}
   110  	}
   111  
   112  	if !reflect.DeepEqual(gotFile.Info(), wantFileInfo) {
   113  		t.Errorf("FileSystem.Open()\ngotFile.Info() = %v\nwantFileInfo = %v", gotFile.Info(), wantFileInfo)
   114  	}
   115  }
   116  
   117  func TestFileSystem_Open(t *testing.T) {
   118  	emptyCompression := xrdfs.FileCompression{}
   119  	entryStat := fstest["/tmp/dir1/file1.txt"]
   120  
   121  	testCases := []struct {
   122  		name        string
   123  		options     xrdfs.OpenOptions
   124  		handle      xrdfs.FileHandle
   125  		compression *xrdfs.FileCompression
   126  		info        *xrdfs.EntryStat
   127  	}{
   128  		{"WithoutCompressionAndStat", xrdfs.OpenOptionsOpenRead, xrdfs.FileHandle{0, 0, 0, 0}, nil, nil},
   129  		{"WithCompression", xrdfs.OpenOptionsOpenRead | xrdfs.OpenOptionsCompress, xrdfs.FileHandle{0, 0, 0, 0}, &emptyCompression, nil},
   130  		{"WithStat", xrdfs.OpenOptionsOpenRead | xrdfs.OpenOptionsReturnStatus, xrdfs.FileHandle{0, 0, 0, 0}, &emptyCompression, entryStat},
   131  	}
   132  
   133  	for i := range testClientAddrs {
   134  		addr := testClientAddrs[i]
   135  		for i := range testCases {
   136  			tc := testCases[i]
   137  			t.Run(addr+"/"+tc.name, func(t *testing.T) {
   138  				t.Parallel()
   139  				testFileSystem_Open(t, addr, tc.options, tc.handle, tc.compression, tc.info)
   140  			})
   141  		}
   142  	}
   143  }
   144  
   145  func testFileSystem_RemoveFile(t *testing.T, addr string) {
   146  	fileName := "rm_test.txt"
   147  
   148  	client, err := NewClient(context.Background(), addr, "gopher")
   149  	if err != nil {
   150  		t.Fatalf("could not create client: %v", err)
   151  	}
   152  	defer client.Close()
   153  	fs := client.FS()
   154  
   155  	dir, err := tempdir(client, "/tmp/", "xrd-test-rm")
   156  	if err != nil {
   157  		t.Fatal(err)
   158  	}
   159  	defer func() {
   160  		_ = fs.RemoveAll(context.Background(), dir)
   161  	}()
   162  	filePath := path.Join(dir, fileName)
   163  
   164  	file, err := fs.Open(context.Background(), filePath, xrdfs.OpenModeOwnerWrite, xrdfs.OpenOptionsDelete)
   165  	if err != nil {
   166  		t.Fatalf("invalid open call: %v", err)
   167  	}
   168  
   169  	file.Close(context.Background())
   170  
   171  	err = fs.RemoveFile(context.Background(), filePath)
   172  	if err != nil {
   173  		t.Fatalf("invalid rm call: %v", err)
   174  	}
   175  
   176  	got, err := fs.Dirlist(context.Background(), dir)
   177  	if err != nil {
   178  		t.Fatalf("invalid dirlist call: %v", err)
   179  	}
   180  
   181  	found := false
   182  	for _, entry := range got {
   183  		if entry.Name() == fileName {
   184  			found = true
   185  		}
   186  	}
   187  
   188  	if found {
   189  		t.Errorf("file '%s' is still present after fs.RemoveFile()", filePath)
   190  	}
   191  }
   192  
   193  func TestFileSystem_RemoveFile(t *testing.T) {
   194  	for i := range testClientAddrs {
   195  		addr := testClientAddrs[i]
   196  		t.Run(addr, func(t *testing.T) {
   197  			t.Parallel()
   198  			testFileSystem_RemoveFile(t, addr)
   199  		})
   200  	}
   201  }
   202  
   203  func testFileSystem_Truncate(t *testing.T, addr string) {
   204  	fileName := "test_truncate_fs.txt"
   205  	write := []uint8{1, 2, 3, 4, 5, 6, 7, 8}
   206  	want := write[:4]
   207  
   208  	client, err := NewClient(context.Background(), addr, "gopher")
   209  	if err != nil {
   210  		t.Fatalf("could not create client: %v", err)
   211  	}
   212  	defer client.Close()
   213  	fs := client.FS()
   214  
   215  	dir, err := tempdir(client, "/tmp/", "xrd-test-truncate")
   216  	if err != nil {
   217  		t.Fatal(err)
   218  	}
   219  	defer func() {
   220  		_ = fs.RemoveAll(context.Background(), dir)
   221  	}()
   222  	filePath := path.Join(dir, fileName)
   223  
   224  	file, err := fs.Open(context.Background(), filePath, xrdfs.OpenModeOwnerWrite, xrdfs.OpenOptionsNew)
   225  	if err != nil {
   226  		t.Fatalf("invalid open call: %v", err)
   227  	}
   228  	defer file.Close(context.Background())
   229  
   230  	_, err = file.WriteAt(write, 0)
   231  	if err != nil {
   232  		t.Fatalf("invalid write call: %v", err)
   233  	}
   234  
   235  	err = file.Sync(context.Background())
   236  	if err != nil {
   237  		t.Fatalf("invalid sync call: %v", err)
   238  	}
   239  
   240  	err = file.Close(context.Background())
   241  	if err != nil {
   242  		t.Fatalf("invalid close call: %v", err)
   243  	}
   244  
   245  	err = fs.Truncate(context.Background(), filePath, int64(len(want)))
   246  	if err != nil {
   247  		t.Fatalf("invalid truncate call: %v", err)
   248  	}
   249  
   250  	file, err = fs.Open(context.Background(), filePath, xrdfs.OpenModeOwnerRead, xrdfs.OpenOptionsOpenRead)
   251  	if err != nil {
   252  		t.Fatalf("invalid open call: %v", err)
   253  	}
   254  	defer file.Close(context.Background())
   255  
   256  	got := make([]uint8, len(want)+10)
   257  	n, err := file.ReadAt(got, 0)
   258  	if err != nil {
   259  		t.Fatalf("invalid read call: %v", err)
   260  	}
   261  
   262  	if n != len(want) {
   263  		t.Fatalf("read count does not match:\ngot = %v\nwant = %v", n, len(want))
   264  	}
   265  
   266  	if !reflect.DeepEqual(got[:n], want) {
   267  		t.Fatalf("read data does not match:\ngot = %v\nwant = %v", got[:n], want)
   268  	}
   269  
   270  }
   271  
   272  func TestFileSystem_Truncate(t *testing.T) {
   273  	for i := range testClientAddrs {
   274  		addr := testClientAddrs[i]
   275  		t.Run(addr, func(t *testing.T) {
   276  			t.Parallel()
   277  			testFileSystem_Truncate(t, addr)
   278  		})
   279  	}
   280  }
   281  
   282  func testFileSystem_Stat(t *testing.T, addr string) {
   283  	want := *fstest["/tmp/dir1/file1.txt"]
   284  
   285  	client, err := NewClient(context.Background(), addr, "gopher")
   286  	if err != nil {
   287  		t.Fatalf("could not create client: %v", err)
   288  	}
   289  	defer client.Close()
   290  
   291  	fs := client.FS()
   292  
   293  	got, err := fs.Stat(context.Background(), "/tmp/dir1/file1.txt")
   294  	if err != nil {
   295  		t.Fatalf("invalid stat call: %v", err)
   296  	}
   297  
   298  	if !reflect.DeepEqual(got, want) {
   299  		t.Errorf("FileSystem.Stat()\ngot = %v\nwant = %v", got, want)
   300  	}
   301  }
   302  
   303  func TestFileSystem_Stat(t *testing.T) {
   304  	for i := range testClientAddrs {
   305  		addr := testClientAddrs[i]
   306  		t.Run(addr, func(t *testing.T) {
   307  			t.Parallel()
   308  			testFileSystem_Stat(t, addr)
   309  		})
   310  	}
   311  }
   312  
   313  func testFileSystem_VirtualStat(t *testing.T, addr string) {
   314  	want := xrdfs.VirtualFSStat{
   315  		NumberRW:      1,
   316  		FreeRW:        365,
   317  		UtilizationRW: 23,
   318  	}
   319  
   320  	client, err := NewClient(context.Background(), addr, "gopher")
   321  	if err != nil {
   322  		t.Fatalf("could not create client: %v", err)
   323  	}
   324  	defer client.Close()
   325  
   326  	fs := client.FS()
   327  
   328  	got, err := fs.VirtualStat(context.Background(), "/tmp/dir1/file1.txt")
   329  	if err != nil {
   330  		t.Fatalf("invalid stat call: %v", err)
   331  	}
   332  
   333  	if got.NumberRW != want.NumberRW {
   334  		t.Errorf("wrong NumberRW:\ngot = %v\nwant = %v", got.NumberRW, want.NumberRW)
   335  	}
   336  
   337  	if got.FreeRW <= 0 || got.FreeRW > 500 {
   338  		t.Errorf("wrong FreeRW:\ngot = %v\nwant to be between 0 and 500", got.FreeRW)
   339  	}
   340  
   341  	if got.UtilizationRW <= 0 || got.UtilizationRW > 100 {
   342  		t.Errorf("wrong UtilizationRW:\ngot = %v\nwant to be between 0 and 100", got.UtilizationRW)
   343  	}
   344  
   345  	if got.NumberStaging != want.NumberStaging {
   346  		t.Errorf("wrong NumberStaging:\ngot = %v\nwant = %v", got.NumberStaging, want.NumberStaging)
   347  	}
   348  	if got.FreeStaging != want.FreeStaging {
   349  		t.Errorf("wrong FreeStaging:\ngot = %v\nwant = %v", got.FreeStaging, want.FreeStaging)
   350  	}
   351  	if got.UtilizationStaging != want.UtilizationStaging {
   352  		t.Errorf("wrong UtilizationStaging:\ngot = %v\nwant = %v", got.UtilizationStaging, want.UtilizationStaging)
   353  	}
   354  }
   355  
   356  func TestFileSystem_VirtualStat(t *testing.T) {
   357  	for i := range testClientAddrs {
   358  		addr := testClientAddrs[i]
   359  		t.Run(addr, func(t *testing.T) {
   360  			t.Parallel()
   361  			testFileSystem_VirtualStat(t, addr)
   362  		})
   363  	}
   364  }
   365  
   366  func testFileSystem_RemoveDir(t *testing.T, addr string) {
   367  	dirName := "test_remove_dir"
   368  
   369  	client, err := NewClient(context.Background(), addr, "gopher")
   370  	if err != nil {
   371  		t.Fatalf("could not create client: %v", err)
   372  	}
   373  	defer client.Close()
   374  	fs := client.FS()
   375  
   376  	parent, err := tempdir(client, "/tmp/", "xrd-test-removedir")
   377  	if err != nil {
   378  		t.Fatal(err)
   379  	}
   380  	defer func() {
   381  		_ = fs.RemoveDir(context.Background(), parent)
   382  	}()
   383  	dir := path.Join(parent, dirName)
   384  
   385  	err = fs.Mkdir(context.Background(), dir, xrdfs.OpenModeOwnerRead|xrdfs.OpenModeOwnerWrite)
   386  	if err != nil {
   387  		t.Fatalf("invalid mkdir call: %v", err)
   388  	}
   389  
   390  	dirs, err := fs.Dirlist(context.Background(), parent)
   391  	if err != nil {
   392  		t.Fatalf("invalid dirlist call: %v", err)
   393  	}
   394  
   395  	found := false
   396  	for _, d := range dirs {
   397  		if d.EntryName == dirName {
   398  			found = true
   399  		}
   400  	}
   401  
   402  	if !found {
   403  		t.Fatalf("dir '%s' has not been created", dir)
   404  	}
   405  
   406  	err = fs.RemoveDir(context.Background(), dir)
   407  	if err != nil {
   408  		t.Fatalf("invalid rmdir call: %v", err)
   409  	}
   410  
   411  	dirs, err = fs.Dirlist(context.Background(), parent)
   412  	if err != nil {
   413  		t.Fatalf("invalid dirlist call: %v", err)
   414  	}
   415  	for _, d := range dirs {
   416  		if d.EntryName == dirName {
   417  			t.Fatalf("dir '%s' has not been deleted", dir)
   418  		}
   419  	}
   420  
   421  }
   422  
   423  func TestFileSystem_RemoveDir(t *testing.T) {
   424  	for i := range testClientAddrs {
   425  		addr := testClientAddrs[i]
   426  		t.Run(addr, func(t *testing.T) {
   427  			t.Parallel()
   428  			testFileSystem_RemoveDir(t, addr)
   429  		})
   430  	}
   431  }
   432  
   433  func TestFileSystem_RemoveAll(t *testing.T) {
   434  	for i := range testClientAddrs {
   435  		addr := testClientAddrs[i]
   436  		t.Run(addr, func(t *testing.T) {
   437  			t.Parallel()
   438  
   439  			dirName := "test_remove_all"
   440  
   441  			client, err := NewClient(context.Background(), addr, "gopher")
   442  			if err != nil {
   443  				t.Fatalf("could not create client: %v", err)
   444  			}
   445  			defer client.Close()
   446  			fs := client.FS()
   447  
   448  			parent, err := tempdir(client, "/tmp/", "xrd-test-remove-all")
   449  			if err != nil {
   450  				t.Fatal(err)
   451  			}
   452  			defer func() {
   453  				_ = fs.RemoveAll(context.Background(), parent)
   454  			}()
   455  			dir := path.Join(parent, dirName)
   456  
   457  			err = fs.Mkdir(context.Background(), dir, xrdfs.OpenModeOwnerRead|xrdfs.OpenModeOwnerWrite)
   458  			if err != nil {
   459  				t.Fatalf("invalid mkdir call: %v", err)
   460  			}
   461  
   462  			dirs, err := fs.Dirlist(context.Background(), parent)
   463  			if err != nil {
   464  				t.Fatalf("invalid dirlist call: %v", err)
   465  			}
   466  
   467  			found := false
   468  			for _, d := range dirs {
   469  				if d.EntryName == dirName {
   470  					found = true
   471  				}
   472  			}
   473  
   474  			if !found {
   475  				t.Fatalf("dir '%s' has not been created", dir)
   476  			}
   477  
   478  			err = fs.RemoveAll(context.Background(), parent)
   479  			if err != nil {
   480  				t.Fatalf("invalid rmdir call: %v", err)
   481  			}
   482  
   483  			dirs, err = fs.Dirlist(context.Background(), "/tmp")
   484  			if err != nil {
   485  				t.Fatalf("invalid dirlist call: %v", err)
   486  			}
   487  			for _, d := range dirs {
   488  				if d.EntryName == path.Base(parent) {
   489  					t.Fatalf("dir '%s' has not been deleted", dir)
   490  				}
   491  			}
   492  		})
   493  	}
   494  }
   495  
   496  func testFileSystem_Rename(t *testing.T, addr string) {
   497  	oldName := "test_rename_before"
   498  	newName := "test_rename_after"
   499  
   500  	client, err := NewClient(context.Background(), addr, "gopher")
   501  	if err != nil {
   502  		t.Fatalf("could not create client: %v", err)
   503  	}
   504  	defer client.Close()
   505  	fs := client.FS()
   506  
   507  	parent, err := tempdir(client, "/tmp/", "xrd-test-rename")
   508  	if err != nil {
   509  		t.Fatal(err)
   510  	}
   511  	oldpath := path.Join(parent, oldName)
   512  	newpath := path.Join(parent, newName)
   513  
   514  	defer func() {
   515  		_ = fs.RemoveDir(context.Background(), newpath)
   516  		_ = fs.RemoveDir(context.Background(), oldpath)
   517  		_ = fs.RemoveAll(context.Background(), parent)
   518  	}()
   519  
   520  	err = fs.Mkdir(context.Background(), oldpath, xrdfs.OpenModeOwnerRead|xrdfs.OpenModeOwnerWrite)
   521  	if err != nil {
   522  		t.Fatalf("invalid mkdir call: %v", err)
   523  	}
   524  
   525  	dirs, err := fs.Dirlist(context.Background(), parent)
   526  	if err != nil {
   527  		t.Fatalf("invalid dirlist call: %v", err)
   528  	}
   529  
   530  	found := false
   531  	for _, d := range dirs {
   532  		if d.EntryName == oldName {
   533  			found = true
   534  			break
   535  		}
   536  	}
   537  
   538  	if !found {
   539  		t.Fatalf("dir %q has not been created", oldpath)
   540  	}
   541  
   542  	err = fs.Rename(context.Background(), oldpath, newpath)
   543  	if err != nil {
   544  		t.Fatalf("invalid rmdir call: %v", err)
   545  	}
   546  
   547  	dirs, err = fs.Dirlist(context.Background(), parent)
   548  	if err != nil {
   549  		t.Fatalf("invalid dirlist call: %v", err)
   550  	}
   551  
   552  	found = false
   553  	for _, d := range dirs {
   554  		if d.EntryName == oldName {
   555  			t.Fatalf("dir %q has not been renamed", oldpath)
   556  		}
   557  		if d.EntryName == newName {
   558  			found = true
   559  		}
   560  	}
   561  
   562  	if !found {
   563  		t.Fatalf("dir %q has not been renamed to %q", oldpath, newpath)
   564  	}
   565  }
   566  
   567  func TestFileSystem_Rename(t *testing.T) {
   568  	for i := range testClientAddrs {
   569  		addr := testClientAddrs[i]
   570  		t.Run(addr, func(t *testing.T) {
   571  			t.Parallel()
   572  			testFileSystem_Rename(t, addr)
   573  		})
   574  	}
   575  }
   576  
   577  func testFileSystem_Chmod(t *testing.T, addr string) {
   578  	name := "test_chmod"
   579  	oldPerm := xrdfs.OpenModeOwnerWrite | xrdfs.OpenModeOwnerRead
   580  	newPerm := xrdfs.OpenModeOwnerRead
   581  
   582  	client, err := NewClient(context.Background(), addr, "gopher")
   583  	if err != nil {
   584  		t.Fatalf("could not create client: %v", err)
   585  	}
   586  	defer client.Close()
   587  	fs := client.FS()
   588  
   589  	parent, err := tempdir(client, "/tmp/", "xrd-test-chmod")
   590  	if err != nil {
   591  		t.Fatal(err)
   592  	}
   593  	defer func() {
   594  		_ = fs.RemoveAll(context.Background(), parent)
   595  	}()
   596  	file := path.Join(parent, name)
   597  
   598  	f, err := fs.Open(context.Background(), file, oldPerm, xrdfs.OpenOptionsNew)
   599  	if err != nil {
   600  		t.Fatalf("could not open file: %v", err)
   601  	}
   602  	err = f.Close(context.Background())
   603  	if err != nil {
   604  		t.Fatalf("could not close file: %v", err)
   605  	}
   606  	defer func() {
   607  		_ = fs.RemoveFile(context.Background(), file)
   608  	}()
   609  
   610  	s, err := fs.Stat(context.Background(), file)
   611  	if err != nil {
   612  		t.Fatalf("invalid stat call: %v", err)
   613  	}
   614  
   615  	if s.Flags&xrdfs.StatIsWritable == 0 {
   616  		t.Fatalf("invalid mode: file should be writable")
   617  	}
   618  
   619  	err = fs.Chmod(context.Background(), file, newPerm)
   620  	if err != nil {
   621  		t.Fatalf("could not chmod %q: %v", file, err)
   622  	}
   623  
   624  	s, err = fs.Stat(context.Background(), file)
   625  	if err != nil {
   626  		t.Fatalf("invalid stat call: %v", err)
   627  	}
   628  
   629  	if s.Flags&xrdfs.StatIsWritable != 0 {
   630  		t.Fatalf("invalid mode: file should not be writable")
   631  	}
   632  
   633  	err = fs.Chmod(context.Background(), file, oldPerm)
   634  	if err != nil {
   635  		t.Fatalf("could not chmod %q: %v", file, err)
   636  	}
   637  
   638  	s, err = fs.Stat(context.Background(), file)
   639  	if err != nil {
   640  		t.Fatalf("invalid stat call: %v", err)
   641  	}
   642  
   643  	if s.Flags&xrdfs.StatIsWritable == 0 {
   644  		t.Fatalf("invalid mode: file should be writable")
   645  	}
   646  }
   647  
   648  func TestFileSystem_Chmod(t *testing.T) {
   649  	for i := range testClientAddrs {
   650  		addr := testClientAddrs[i]
   651  		t.Run(addr, func(t *testing.T) {
   652  			t.Parallel()
   653  			testFileSystem_Chmod(t, addr)
   654  		})
   655  	}
   656  }
   657  
   658  func testFileSystem_Statx(t *testing.T, addr string) {
   659  	want := []xrdfs.StatFlags{xrdfs.StatIsFile, xrdfs.StatIsDir}
   660  
   661  	client, err := NewClient(context.Background(), addr, "gopher")
   662  	if err != nil {
   663  		t.Fatalf("could not create client: %v", err)
   664  	}
   665  	defer client.Close()
   666  
   667  	fs := client.FS()
   668  
   669  	got, err := fs.Statx(context.Background(), []string{"/tmp/dir1/file1.txt", "/tmp/dir1"})
   670  	if err != nil {
   671  		t.Fatalf("invalid statx call: %v", err)
   672  	}
   673  
   674  	if !reflect.DeepEqual(got, want) {
   675  		t.Errorf("FileSystem.Statx()\ngot = %v\nwant = %v", got, want)
   676  	}
   677  }
   678  
   679  func TestFileSystem_Statx(t *testing.T) {
   680  	for i := range testClientAddrs {
   681  		addr := testClientAddrs[i]
   682  		t.Run(addr, func(t *testing.T) {
   683  			t.Parallel()
   684  			testFileSystem_Statx(t, addr)
   685  		})
   686  	}
   687  }
   688  
   689  func ExampleClient_dirlist() {
   690  	ctx := context.Background()
   691  	const username = "gopher"
   692  	client, err := NewClient(ctx, "ccxrootdgotest.in2p3.fr:9001", username)
   693  	if err != nil {
   694  		log.Fatal(err)
   695  	}
   696  	defer client.Close()
   697  
   698  	entries, err := client.FS().Dirlist(ctx, "/tmp/dir1")
   699  	if err != nil {
   700  		log.Fatal(err)
   701  	}
   702  	for _, entry := range entries {
   703  		fmt.Printf("Name: %s, size: %d\n", entry.Name(), entry.Size())
   704  	}
   705  
   706  	if err := client.Close(); err != nil {
   707  		log.Fatal(err)
   708  	}
   709  
   710  	// Output:
   711  	// Name: file1.txt, size: 0
   712  }
   713  
   714  func ExampleClient_open() {
   715  	ctx := context.Background()
   716  	const username = "gopher"
   717  	client, err := NewClient(ctx, "ccxrootdgotest.in2p3.fr:9001", username)
   718  	if err != nil {
   719  		log.Fatal(err)
   720  	}
   721  	defer client.Close()
   722  
   723  	file, err := client.FS().Open(ctx, "/tmp/test.txt", xrdfs.OpenModeOwnerRead, xrdfs.OpenOptionsOpenRead)
   724  	if err != nil {
   725  		log.Fatal(err)
   726  	}
   727  
   728  	if err := file.Close(ctx); err != nil {
   729  		log.Fatal(err)
   730  	}
   731  
   732  	if err := client.Close(); err != nil {
   733  		log.Fatal(err)
   734  	}
   735  }
   736  
   737  func ExampleClient_removeFile() {
   738  	ctx := context.Background()
   739  	const username = "gopher"
   740  	client, err := NewClient(ctx, "ccxrootdgotest.in2p3.fr:9001", username)
   741  	if err != nil {
   742  		log.Fatal(err)
   743  	}
   744  	defer client.Close()
   745  
   746  	if err := client.FS().RemoveFile(ctx, "/tmp/test.txt"); err != nil {
   747  		log.Fatal(err)
   748  	}
   749  
   750  	if err := client.Close(); err != nil {
   751  		log.Fatal(err)
   752  	}
   753  }
   754  
   755  func ExampleClient_truncate() {
   756  	ctx := context.Background()
   757  	const username = "gopher"
   758  	client, err := NewClient(ctx, "ccxrootdgotest.in2p3.fr:9001", username)
   759  	if err != nil {
   760  		log.Fatal(err)
   761  	}
   762  	defer client.Close()
   763  
   764  	if err := client.FS().Truncate(ctx, "/tmp/test.txt", 10); err != nil {
   765  		log.Fatal(err)
   766  	}
   767  
   768  	if err := client.Close(); err != nil {
   769  		log.Fatal(err)
   770  	}
   771  }
   772  
   773  func ExampleClient_stat() {
   774  	ctx := context.Background()
   775  	const username = "gopher"
   776  	client, err := NewClient(ctx, "ccxrootdgotest.in2p3.fr:9001", username)
   777  	if err != nil {
   778  		log.Fatal(err)
   779  	}
   780  	defer client.Close()
   781  
   782  	info, err := client.FS().Stat(ctx, "/tmp/test.txt")
   783  	if err != nil {
   784  		log.Fatal(err)
   785  	}
   786  
   787  	log.Printf("Name: %s, size: %d", info.Name(), info.Size())
   788  
   789  	if err := client.Close(); err != nil {
   790  		log.Fatal(err)
   791  	}
   792  }
   793  
   794  func ExampleClient_virtualStat() {
   795  	ctx := context.Background()
   796  	const username = "gopher"
   797  	client, err := NewClient(ctx, "ccxrootdgotest.in2p3.fr:9001", username)
   798  	if err != nil {
   799  		log.Fatal(err)
   800  	}
   801  	defer client.Close()
   802  
   803  	info, err := client.FS().VirtualStat(ctx, "/tmp/")
   804  	if err != nil {
   805  		log.Fatal(err)
   806  	}
   807  
   808  	log.Printf("RW: %d%% is free", info.FreeRW)
   809  
   810  	if err := client.Close(); err != nil {
   811  		log.Fatal(err)
   812  	}
   813  }
   814  
   815  func ExampleClient_mkdir() {
   816  	ctx := context.Background()
   817  	const username = "gopher"
   818  	client, err := NewClient(ctx, "ccxrootdgotest.in2p3.fr:9001", username)
   819  	if err != nil {
   820  		log.Fatal(err)
   821  	}
   822  	defer client.Close()
   823  
   824  	if err := client.FS().Mkdir(ctx, "/tmp/testdir", xrdfs.OpenModeOwnerRead|xrdfs.OpenModeOwnerWrite); err != nil {
   825  		log.Fatal(err)
   826  	}
   827  
   828  	if err := client.Close(); err != nil {
   829  		log.Fatal(err)
   830  	}
   831  }
   832  
   833  func ExampleClient_mkdirAll() {
   834  	ctx := context.Background()
   835  	const username = "gopher"
   836  	client, err := NewClient(ctx, "ccxrootdgotest.in2p3.fr:9001", username)
   837  	if err != nil {
   838  		log.Fatal(err)
   839  	}
   840  	defer client.Close()
   841  
   842  	if err := client.FS().MkdirAll(ctx, "/tmp/testdir/subdir", xrdfs.OpenModeOwnerRead|xrdfs.OpenModeOwnerWrite); err != nil {
   843  		log.Fatal(err)
   844  	}
   845  
   846  	if err := client.Close(); err != nil {
   847  		log.Fatal(err)
   848  	}
   849  }
   850  
   851  func ExampleClient_removeDir() {
   852  	ctx := context.Background()
   853  	const username = "gopher"
   854  	client, err := NewClient(ctx, "ccxrootdgotest.in2p3.fr:9001", username)
   855  	if err != nil {
   856  		log.Fatal(err)
   857  	}
   858  	defer client.Close()
   859  
   860  	if err := client.FS().RemoveDir(ctx, "/tmp/testdir"); err != nil {
   861  		log.Fatal(err)
   862  	}
   863  
   864  	if err := client.Close(); err != nil {
   865  		log.Fatal(err)
   866  	}
   867  }
   868  
   869  func ExampleClient_removeAll() {
   870  	ctx := context.Background()
   871  	const username = "gopher"
   872  	client, err := NewClient(ctx, "ccxrootdgotest.in2p3.fr:9001", username)
   873  	if err != nil {
   874  		log.Fatal(err)
   875  	}
   876  	defer client.Close()
   877  
   878  	if err := client.FS().RemoveAll(ctx, "/tmp/testdir"); err != nil {
   879  		log.Fatal(err)
   880  	}
   881  
   882  	if err := client.Close(); err != nil {
   883  		log.Fatal(err)
   884  	}
   885  }
   886  
   887  func ExampleClient_rename() {
   888  	ctx := context.Background()
   889  	const username = "gopher"
   890  	client, err := NewClient(ctx, "ccxrootdgotest.in2p3.fr:9001", username)
   891  	if err != nil {
   892  		log.Fatal(err)
   893  	}
   894  	defer client.Close()
   895  
   896  	if err := client.FS().Rename(ctx, "/tmp/old.txt", "/tmp/new.txt"); err != nil {
   897  		log.Fatal(err)
   898  	}
   899  
   900  	if err := client.Close(); err != nil {
   901  		log.Fatal(err)
   902  	}
   903  }
   904  
   905  func ExampleClient_chmod() {
   906  	ctx := context.Background()
   907  	const username = "gopher"
   908  	client, err := NewClient(ctx, "ccxrootdgotest.in2p3.fr:9001", username)
   909  	if err != nil {
   910  		log.Fatal(err)
   911  	}
   912  	defer client.Close()
   913  
   914  	if err := client.FS().Chmod(ctx, "/tmp/test.txt", xrdfs.OpenModeOwnerRead|xrdfs.OpenModeOwnerWrite); err != nil {
   915  		log.Fatal(err)
   916  	}
   917  
   918  	if err := client.Close(); err != nil {
   919  		log.Fatal(err)
   920  	}
   921  }