...

Source file src/testing/fstest/testfs.go

Documentation: testing/fstest

		 1  // Copyright 2020 The Go 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 fstest implements support for testing implementations and users of file systems.
		 6  package fstest
		 7  
		 8  import (
		 9  	"errors"
		10  	"fmt"
		11  	"io"
		12  	"io/fs"
		13  	"path"
		14  	"reflect"
		15  	"sort"
		16  	"strings"
		17  	"testing/iotest"
		18  )
		19  
		20  // TestFS tests a file system implementation.
		21  // It walks the entire tree of files in fsys,
		22  // opening and checking that each file behaves correctly.
		23  // It also checks that the file system contains at least the expected files.
		24  // As a special case, if no expected files are listed, fsys must be empty.
		25  // Otherwise, fsys must contain at least the listed files; it can also contain others.
		26  // The contents of fsys must not change concurrently with TestFS.
		27  //
		28  // If TestFS finds any misbehaviors, it returns an error reporting all of them.
		29  // The error text spans multiple lines, one per detected misbehavior.
		30  //
		31  // Typical usage inside a test is:
		32  //
		33  //	if err := fstest.TestFS(myFS, "file/that/should/be/present"); err != nil {
		34  //		t.Fatal(err)
		35  //	}
		36  //
		37  func TestFS(fsys fs.FS, expected ...string) error {
		38  	if err := testFS(fsys, expected...); err != nil {
		39  		return err
		40  	}
		41  	for _, name := range expected {
		42  		if i := strings.Index(name, "/"); i >= 0 {
		43  			dir, dirSlash := name[:i], name[:i+1]
		44  			var subExpected []string
		45  			for _, name := range expected {
		46  				if strings.HasPrefix(name, dirSlash) {
		47  					subExpected = append(subExpected, name[len(dirSlash):])
		48  				}
		49  			}
		50  			sub, err := fs.Sub(fsys, dir)
		51  			if err != nil {
		52  				return err
		53  			}
		54  			if err := testFS(sub, subExpected...); err != nil {
		55  				return fmt.Errorf("testing fs.Sub(fsys, %s): %v", dir, err)
		56  			}
		57  			break // one sub-test is enough
		58  		}
		59  	}
		60  	return nil
		61  }
		62  
		63  func testFS(fsys fs.FS, expected ...string) error {
		64  	t := fsTester{fsys: fsys}
		65  	t.checkDir(".")
		66  	t.checkOpen(".")
		67  	found := make(map[string]bool)
		68  	for _, dir := range t.dirs {
		69  		found[dir] = true
		70  	}
		71  	for _, file := range t.files {
		72  		found[file] = true
		73  	}
		74  	delete(found, ".")
		75  	if len(expected) == 0 && len(found) > 0 {
		76  		var list []string
		77  		for k := range found {
		78  			if k != "." {
		79  				list = append(list, k)
		80  			}
		81  		}
		82  		sort.Strings(list)
		83  		if len(list) > 15 {
		84  			list = append(list[:10], "...")
		85  		}
		86  		t.errorf("expected empty file system but found files:\n%s", strings.Join(list, "\n"))
		87  	}
		88  	for _, name := range expected {
		89  		if !found[name] {
		90  			t.errorf("expected but not found: %s", name)
		91  		}
		92  	}
		93  	if len(t.errText) == 0 {
		94  		return nil
		95  	}
		96  	return errors.New("TestFS found errors:\n" + string(t.errText))
		97  }
		98  
		99  // An fsTester holds state for running the test.
	 100  type fsTester struct {
	 101  	fsys		fs.FS
	 102  	errText []byte
	 103  	dirs		[]string
	 104  	files	 []string
	 105  }
	 106  
	 107  // errorf adds an error line to errText.
	 108  func (t *fsTester) errorf(format string, args ...interface{}) {
	 109  	if len(t.errText) > 0 {
	 110  		t.errText = append(t.errText, '\n')
	 111  	}
	 112  	t.errText = append(t.errText, fmt.Sprintf(format, args...)...)
	 113  }
	 114  
	 115  func (t *fsTester) openDir(dir string) fs.ReadDirFile {
	 116  	f, err := t.fsys.Open(dir)
	 117  	if err != nil {
	 118  		t.errorf("%s: Open: %v", dir, err)
	 119  		return nil
	 120  	}
	 121  	d, ok := f.(fs.ReadDirFile)
	 122  	if !ok {
	 123  		f.Close()
	 124  		t.errorf("%s: Open returned File type %T, not a fs.ReadDirFile", dir, f)
	 125  		return nil
	 126  	}
	 127  	return d
	 128  }
	 129  
	 130  // checkDir checks the directory dir, which is expected to exist
	 131  // (it is either the root or was found in a directory listing with IsDir true).
	 132  func (t *fsTester) checkDir(dir string) {
	 133  	// Read entire directory.
	 134  	t.dirs = append(t.dirs, dir)
	 135  	d := t.openDir(dir)
	 136  	if d == nil {
	 137  		return
	 138  	}
	 139  	list, err := d.ReadDir(-1)
	 140  	if err != nil {
	 141  		d.Close()
	 142  		t.errorf("%s: ReadDir(-1): %v", dir, err)
	 143  		return
	 144  	}
	 145  
	 146  	// Check all children.
	 147  	var prefix string
	 148  	if dir == "." {
	 149  		prefix = ""
	 150  	} else {
	 151  		prefix = dir + "/"
	 152  	}
	 153  	for _, info := range list {
	 154  		name := info.Name()
	 155  		switch {
	 156  		case name == ".", name == "..", name == "":
	 157  			t.errorf("%s: ReadDir: child has invalid name: %#q", dir, name)
	 158  			continue
	 159  		case strings.Contains(name, "/"):
	 160  			t.errorf("%s: ReadDir: child name contains slash: %#q", dir, name)
	 161  			continue
	 162  		case strings.Contains(name, `\`):
	 163  			t.errorf("%s: ReadDir: child name contains backslash: %#q", dir, name)
	 164  			continue
	 165  		}
	 166  		path := prefix + name
	 167  		t.checkStat(path, info)
	 168  		t.checkOpen(path)
	 169  		if info.IsDir() {
	 170  			t.checkDir(path)
	 171  		} else {
	 172  			t.checkFile(path)
	 173  		}
	 174  	}
	 175  
	 176  	// Check ReadDir(-1) at EOF.
	 177  	list2, err := d.ReadDir(-1)
	 178  	if len(list2) > 0 || err != nil {
	 179  		d.Close()
	 180  		t.errorf("%s: ReadDir(-1) at EOF = %d entries, %v, wanted 0 entries, nil", dir, len(list2), err)
	 181  		return
	 182  	}
	 183  
	 184  	// Check ReadDir(1) at EOF (different results).
	 185  	list2, err = d.ReadDir(1)
	 186  	if len(list2) > 0 || err != io.EOF {
	 187  		d.Close()
	 188  		t.errorf("%s: ReadDir(1) at EOF = %d entries, %v, wanted 0 entries, EOF", dir, len(list2), err)
	 189  		return
	 190  	}
	 191  
	 192  	// Check that close does not report an error.
	 193  	if err := d.Close(); err != nil {
	 194  		t.errorf("%s: Close: %v", dir, err)
	 195  	}
	 196  
	 197  	// Check that closing twice doesn't crash.
	 198  	// The return value doesn't matter.
	 199  	d.Close()
	 200  
	 201  	// Reopen directory, read a second time, make sure contents match.
	 202  	if d = t.openDir(dir); d == nil {
	 203  		return
	 204  	}
	 205  	defer d.Close()
	 206  	list2, err = d.ReadDir(-1)
	 207  	if err != nil {
	 208  		t.errorf("%s: second Open+ReadDir(-1): %v", dir, err)
	 209  		return
	 210  	}
	 211  	t.checkDirList(dir, "first Open+ReadDir(-1) vs second Open+ReadDir(-1)", list, list2)
	 212  
	 213  	// Reopen directory, read a third time in pieces, make sure contents match.
	 214  	if d = t.openDir(dir); d == nil {
	 215  		return
	 216  	}
	 217  	defer d.Close()
	 218  	list2 = nil
	 219  	for {
	 220  		n := 1
	 221  		if len(list2) > 0 {
	 222  			n = 2
	 223  		}
	 224  		frag, err := d.ReadDir(n)
	 225  		if len(frag) > n {
	 226  			t.errorf("%s: third Open: ReadDir(%d) after %d: %d entries (too many)", dir, n, len(list2), len(frag))
	 227  			return
	 228  		}
	 229  		list2 = append(list2, frag...)
	 230  		if err == io.EOF {
	 231  			break
	 232  		}
	 233  		if err != nil {
	 234  			t.errorf("%s: third Open: ReadDir(%d) after %d: %v", dir, n, len(list2), err)
	 235  			return
	 236  		}
	 237  		if n == 0 {
	 238  			t.errorf("%s: third Open: ReadDir(%d) after %d: 0 entries but nil error", dir, n, len(list2))
	 239  			return
	 240  		}
	 241  	}
	 242  	t.checkDirList(dir, "first Open+ReadDir(-1) vs third Open+ReadDir(1,2) loop", list, list2)
	 243  
	 244  	// If fsys has ReadDir, check that it matches and is sorted.
	 245  	if fsys, ok := t.fsys.(fs.ReadDirFS); ok {
	 246  		list2, err := fsys.ReadDir(dir)
	 247  		if err != nil {
	 248  			t.errorf("%s: fsys.ReadDir: %v", dir, err)
	 249  			return
	 250  		}
	 251  		t.checkDirList(dir, "first Open+ReadDir(-1) vs fsys.ReadDir", list, list2)
	 252  
	 253  		for i := 0; i+1 < len(list2); i++ {
	 254  			if list2[i].Name() >= list2[i+1].Name() {
	 255  				t.errorf("%s: fsys.ReadDir: list not sorted: %s before %s", dir, list2[i].Name(), list2[i+1].Name())
	 256  			}
	 257  		}
	 258  	}
	 259  
	 260  	// Check fs.ReadDir as well.
	 261  	list2, err = fs.ReadDir(t.fsys, dir)
	 262  	if err != nil {
	 263  		t.errorf("%s: fs.ReadDir: %v", dir, err)
	 264  		return
	 265  	}
	 266  	t.checkDirList(dir, "first Open+ReadDir(-1) vs fs.ReadDir", list, list2)
	 267  
	 268  	for i := 0; i+1 < len(list2); i++ {
	 269  		if list2[i].Name() >= list2[i+1].Name() {
	 270  			t.errorf("%s: fs.ReadDir: list not sorted: %s before %s", dir, list2[i].Name(), list2[i+1].Name())
	 271  		}
	 272  	}
	 273  
	 274  	t.checkGlob(dir, list)
	 275  }
	 276  
	 277  // formatEntry formats an fs.DirEntry into a string for error messages and comparison.
	 278  func formatEntry(entry fs.DirEntry) string {
	 279  	return fmt.Sprintf("%s IsDir=%v Type=%v", entry.Name(), entry.IsDir(), entry.Type())
	 280  }
	 281  
	 282  // formatInfoEntry formats an fs.FileInfo into a string like the result of formatEntry, for error messages and comparison.
	 283  func formatInfoEntry(info fs.FileInfo) string {
	 284  	return fmt.Sprintf("%s IsDir=%v Type=%v", info.Name(), info.IsDir(), info.Mode().Type())
	 285  }
	 286  
	 287  // formatInfo formats an fs.FileInfo into a string for error messages and comparison.
	 288  func formatInfo(info fs.FileInfo) string {
	 289  	return fmt.Sprintf("%s IsDir=%v Mode=%v Size=%d ModTime=%v", info.Name(), info.IsDir(), info.Mode(), info.Size(), info.ModTime())
	 290  }
	 291  
	 292  // checkGlob checks that various glob patterns work if the file system implements GlobFS.
	 293  func (t *fsTester) checkGlob(dir string, list []fs.DirEntry) {
	 294  	if _, ok := t.fsys.(fs.GlobFS); !ok {
	 295  		return
	 296  	}
	 297  
	 298  	// Make a complex glob pattern prefix that only matches dir.
	 299  	var glob string
	 300  	if dir != "." {
	 301  		elem := strings.Split(dir, "/")
	 302  		for i, e := range elem {
	 303  			var pattern []rune
	 304  			for j, r := range e {
	 305  				if r == '*' || r == '?' || r == '\\' || r == '[' || r == '-' {
	 306  					pattern = append(pattern, '\\', r)
	 307  					continue
	 308  				}
	 309  				switch (i + j) % 5 {
	 310  				case 0:
	 311  					pattern = append(pattern, r)
	 312  				case 1:
	 313  					pattern = append(pattern, '[', r, ']')
	 314  				case 2:
	 315  					pattern = append(pattern, '[', r, '-', r, ']')
	 316  				case 3:
	 317  					pattern = append(pattern, '[', '\\', r, ']')
	 318  				case 4:
	 319  					pattern = append(pattern, '[', '\\', r, '-', '\\', r, ']')
	 320  				}
	 321  			}
	 322  			elem[i] = string(pattern)
	 323  		}
	 324  		glob = strings.Join(elem, "/") + "/"
	 325  	}
	 326  
	 327  	// Test that malformed patterns are detected.
	 328  	// The error is likely path.ErrBadPattern but need not be.
	 329  	if _, err := t.fsys.(fs.GlobFS).Glob(glob + "nonexist/[]"); err == nil {
	 330  		t.errorf("%s: Glob(%#q): bad pattern not detected", dir, glob+"nonexist/[]")
	 331  	}
	 332  
	 333  	// Try to find a letter that appears in only some of the final names.
	 334  	c := rune('a')
	 335  	for ; c <= 'z'; c++ {
	 336  		have, haveNot := false, false
	 337  		for _, d := range list {
	 338  			if strings.ContainsRune(d.Name(), c) {
	 339  				have = true
	 340  			} else {
	 341  				haveNot = true
	 342  			}
	 343  		}
	 344  		if have && haveNot {
	 345  			break
	 346  		}
	 347  	}
	 348  	if c > 'z' {
	 349  		c = 'a'
	 350  	}
	 351  	glob += "*" + string(c) + "*"
	 352  
	 353  	var want []string
	 354  	for _, d := range list {
	 355  		if strings.ContainsRune(d.Name(), c) {
	 356  			want = append(want, path.Join(dir, d.Name()))
	 357  		}
	 358  	}
	 359  
	 360  	names, err := t.fsys.(fs.GlobFS).Glob(glob)
	 361  	if err != nil {
	 362  		t.errorf("%s: Glob(%#q): %v", dir, glob, err)
	 363  		return
	 364  	}
	 365  	if reflect.DeepEqual(want, names) {
	 366  		return
	 367  	}
	 368  
	 369  	if !sort.StringsAreSorted(names) {
	 370  		t.errorf("%s: Glob(%#q): unsorted output:\n%s", dir, glob, strings.Join(names, "\n"))
	 371  		sort.Strings(names)
	 372  	}
	 373  
	 374  	var problems []string
	 375  	for len(want) > 0 || len(names) > 0 {
	 376  		switch {
	 377  		case len(want) > 0 && len(names) > 0 && want[0] == names[0]:
	 378  			want, names = want[1:], names[1:]
	 379  		case len(want) > 0 && (len(names) == 0 || want[0] < names[0]):
	 380  			problems = append(problems, "missing: "+want[0])
	 381  			want = want[1:]
	 382  		default:
	 383  			problems = append(problems, "extra: "+names[0])
	 384  			names = names[1:]
	 385  		}
	 386  	}
	 387  	t.errorf("%s: Glob(%#q): wrong output:\n%s", dir, glob, strings.Join(problems, "\n"))
	 388  }
	 389  
	 390  // checkStat checks that a direct stat of path matches entry,
	 391  // which was found in the parent's directory listing.
	 392  func (t *fsTester) checkStat(path string, entry fs.DirEntry) {
	 393  	file, err := t.fsys.Open(path)
	 394  	if err != nil {
	 395  		t.errorf("%s: Open: %v", path, err)
	 396  		return
	 397  	}
	 398  	info, err := file.Stat()
	 399  	file.Close()
	 400  	if err != nil {
	 401  		t.errorf("%s: Stat: %v", path, err)
	 402  		return
	 403  	}
	 404  	fentry := formatEntry(entry)
	 405  	fientry := formatInfoEntry(info)
	 406  	// Note: mismatch here is OK for symlink, because Open dereferences symlink.
	 407  	if fentry != fientry && entry.Type()&fs.ModeSymlink == 0 {
	 408  		t.errorf("%s: mismatch:\n\tentry = %s\n\tfile.Stat() = %s", path, fentry, fientry)
	 409  	}
	 410  
	 411  	einfo, err := entry.Info()
	 412  	if err != nil {
	 413  		t.errorf("%s: entry.Info: %v", path, err)
	 414  		return
	 415  	}
	 416  	finfo := formatInfo(info)
	 417  	if entry.Type()&fs.ModeSymlink != 0 {
	 418  		// For symlink, just check that entry.Info matches entry on common fields.
	 419  		// Open deferences symlink, so info itself may differ.
	 420  		feentry := formatInfoEntry(einfo)
	 421  		if fentry != feentry {
	 422  			t.errorf("%s: mismatch\n\tentry = %s\n\tentry.Info() = %s\n", path, fentry, feentry)
	 423  		}
	 424  	} else {
	 425  		feinfo := formatInfo(einfo)
	 426  		if feinfo != finfo {
	 427  			t.errorf("%s: mismatch:\n\tentry.Info() = %s\n\tfile.Stat() = %s\n", path, feinfo, finfo)
	 428  		}
	 429  	}
	 430  
	 431  	// Stat should be the same as Open+Stat, even for symlinks.
	 432  	info2, err := fs.Stat(t.fsys, path)
	 433  	if err != nil {
	 434  		t.errorf("%s: fs.Stat: %v", path, err)
	 435  		return
	 436  	}
	 437  	finfo2 := formatInfo(info2)
	 438  	if finfo2 != finfo {
	 439  		t.errorf("%s: fs.Stat(...) = %s\n\twant %s", path, finfo2, finfo)
	 440  	}
	 441  
	 442  	if fsys, ok := t.fsys.(fs.StatFS); ok {
	 443  		info2, err := fsys.Stat(path)
	 444  		if err != nil {
	 445  			t.errorf("%s: fsys.Stat: %v", path, err)
	 446  			return
	 447  		}
	 448  		finfo2 := formatInfo(info2)
	 449  		if finfo2 != finfo {
	 450  			t.errorf("%s: fsys.Stat(...) = %s\n\twant %s", path, finfo2, finfo)
	 451  		}
	 452  	}
	 453  }
	 454  
	 455  // checkDirList checks that two directory lists contain the same files and file info.
	 456  // The order of the lists need not match.
	 457  func (t *fsTester) checkDirList(dir, desc string, list1, list2 []fs.DirEntry) {
	 458  	old := make(map[string]fs.DirEntry)
	 459  	checkMode := func(entry fs.DirEntry) {
	 460  		if entry.IsDir() != (entry.Type()&fs.ModeDir != 0) {
	 461  			if entry.IsDir() {
	 462  				t.errorf("%s: ReadDir returned %s with IsDir() = true, Type() & ModeDir = 0", dir, entry.Name())
	 463  			} else {
	 464  				t.errorf("%s: ReadDir returned %s with IsDir() = false, Type() & ModeDir = ModeDir", dir, entry.Name())
	 465  			}
	 466  		}
	 467  	}
	 468  
	 469  	for _, entry1 := range list1 {
	 470  		old[entry1.Name()] = entry1
	 471  		checkMode(entry1)
	 472  	}
	 473  
	 474  	var diffs []string
	 475  	for _, entry2 := range list2 {
	 476  		entry1 := old[entry2.Name()]
	 477  		if entry1 == nil {
	 478  			checkMode(entry2)
	 479  			diffs = append(diffs, "+ "+formatEntry(entry2))
	 480  			continue
	 481  		}
	 482  		if formatEntry(entry1) != formatEntry(entry2) {
	 483  			diffs = append(diffs, "- "+formatEntry(entry1), "+ "+formatEntry(entry2))
	 484  		}
	 485  		delete(old, entry2.Name())
	 486  	}
	 487  	for _, entry1 := range old {
	 488  		diffs = append(diffs, "- "+formatEntry(entry1))
	 489  	}
	 490  
	 491  	if len(diffs) == 0 {
	 492  		return
	 493  	}
	 494  
	 495  	sort.Slice(diffs, func(i, j int) bool {
	 496  		fi := strings.Fields(diffs[i])
	 497  		fj := strings.Fields(diffs[j])
	 498  		// sort by name (i < j) and then +/- (j < i, because + < -)
	 499  		return fi[1]+" "+fj[0] < fj[1]+" "+fi[0]
	 500  	})
	 501  
	 502  	t.errorf("%s: diff %s:\n\t%s", dir, desc, strings.Join(diffs, "\n\t"))
	 503  }
	 504  
	 505  // checkFile checks that basic file reading works correctly.
	 506  func (t *fsTester) checkFile(file string) {
	 507  	t.files = append(t.files, file)
	 508  
	 509  	// Read entire file.
	 510  	f, err := t.fsys.Open(file)
	 511  	if err != nil {
	 512  		t.errorf("%s: Open: %v", file, err)
	 513  		return
	 514  	}
	 515  
	 516  	data, err := io.ReadAll(f)
	 517  	if err != nil {
	 518  		f.Close()
	 519  		t.errorf("%s: Open+ReadAll: %v", file, err)
	 520  		return
	 521  	}
	 522  
	 523  	if err := f.Close(); err != nil {
	 524  		t.errorf("%s: Close: %v", file, err)
	 525  	}
	 526  
	 527  	// Check that closing twice doesn't crash.
	 528  	// The return value doesn't matter.
	 529  	f.Close()
	 530  
	 531  	// Check that ReadFile works if present.
	 532  	if fsys, ok := t.fsys.(fs.ReadFileFS); ok {
	 533  		data2, err := fsys.ReadFile(file)
	 534  		if err != nil {
	 535  			t.errorf("%s: fsys.ReadFile: %v", file, err)
	 536  			return
	 537  		}
	 538  		t.checkFileRead(file, "ReadAll vs fsys.ReadFile", data, data2)
	 539  
	 540  		// Modify the data and check it again. Modifying the
	 541  		// returned byte slice should not affect the next call.
	 542  		for i := range data2 {
	 543  			data2[i]++
	 544  		}
	 545  		data2, err = fsys.ReadFile(file)
	 546  		if err != nil {
	 547  			t.errorf("%s: second call to fsys.ReadFile: %v", file, err)
	 548  			return
	 549  		}
	 550  		t.checkFileRead(file, "Readall vs second fsys.ReadFile", data, data2)
	 551  
	 552  		t.checkBadPath(file, "ReadFile",
	 553  			func(name string) error { _, err := fsys.ReadFile(name); return err })
	 554  	}
	 555  
	 556  	// Check that fs.ReadFile works with t.fsys.
	 557  	data2, err := fs.ReadFile(t.fsys, file)
	 558  	if err != nil {
	 559  		t.errorf("%s: fs.ReadFile: %v", file, err)
	 560  		return
	 561  	}
	 562  	t.checkFileRead(file, "ReadAll vs fs.ReadFile", data, data2)
	 563  
	 564  	// Use iotest.TestReader to check small reads, Seek, ReadAt.
	 565  	f, err = t.fsys.Open(file)
	 566  	if err != nil {
	 567  		t.errorf("%s: second Open: %v", file, err)
	 568  		return
	 569  	}
	 570  	defer f.Close()
	 571  	if err := iotest.TestReader(f, data); err != nil {
	 572  		t.errorf("%s: failed TestReader:\n\t%s", file, strings.ReplaceAll(err.Error(), "\n", "\n\t"))
	 573  	}
	 574  }
	 575  
	 576  func (t *fsTester) checkFileRead(file, desc string, data1, data2 []byte) {
	 577  	if string(data1) != string(data2) {
	 578  		t.errorf("%s: %s: different data returned\n\t%q\n\t%q", file, desc, data1, data2)
	 579  		return
	 580  	}
	 581  }
	 582  
	 583  // checkBadPath checks that various invalid forms of file's name cannot be opened using t.fsys.Open.
	 584  func (t *fsTester) checkOpen(file string) {
	 585  	t.checkBadPath(file, "Open", func(file string) error {
	 586  		f, err := t.fsys.Open(file)
	 587  		if err == nil {
	 588  			f.Close()
	 589  		}
	 590  		return err
	 591  	})
	 592  }
	 593  
	 594  // checkBadPath checks that various invalid forms of file's name cannot be opened using open.
	 595  func (t *fsTester) checkBadPath(file string, desc string, open func(string) error) {
	 596  	bad := []string{
	 597  		"/" + file,
	 598  		file + "/.",
	 599  	}
	 600  	if file == "." {
	 601  		bad = append(bad, "/")
	 602  	}
	 603  	if i := strings.Index(file, "/"); i >= 0 {
	 604  		bad = append(bad,
	 605  			file[:i]+"//"+file[i+1:],
	 606  			file[:i]+"/./"+file[i+1:],
	 607  			file[:i]+`\`+file[i+1:],
	 608  			file[:i]+"/../"+file,
	 609  		)
	 610  	}
	 611  	if i := strings.LastIndex(file, "/"); i >= 0 {
	 612  		bad = append(bad,
	 613  			file[:i]+"//"+file[i+1:],
	 614  			file[:i]+"/./"+file[i+1:],
	 615  			file[:i]+`\`+file[i+1:],
	 616  			file+"/../"+file[i+1:],
	 617  		)
	 618  	}
	 619  
	 620  	for _, b := range bad {
	 621  		if err := open(b); err == nil {
	 622  			t.errorf("%s: %s(%s) succeeded, want error", file, desc, b)
	 623  		}
	 624  	}
	 625  }
	 626  

View as plain text