github.com/go4org/go4@v0.0.0-20200104003542-c7e774b10ea0/xdgdir/xdgdir_test.go (about)

     1  /*
     2  Copyright 2017 The go4 Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8       http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package xdgdir
    18  
    19  import (
    20  	"io/ioutil"
    21  	"os"
    22  	"path/filepath"
    23  	"testing"
    24  )
    25  
    26  func TestDir_Path(t *testing.T) {
    27  	td := newTempDir(t)
    28  	defer td.cleanup()
    29  	allopenDir := td.mkdir("allopen", 0777)
    30  	readonlyDir := td.mkdir("readonly", 0400)
    31  	secureDir := td.mkdir("secure", 0700)
    32  
    33  	tests := []struct {
    34  		dir     Dir
    35  		env     env
    36  		path    string
    37  		geteuid func() int
    38  	}{
    39  		{
    40  			dir:  Data,
    41  			env:  env{"HOME": "/xHOMEx/me", "XDG_DATA_HOME": "/foo/data"},
    42  			path: "/foo/data",
    43  		},
    44  		{
    45  			dir:  Data,
    46  			env:  env{"HOME": "/xHOMEx/me"},
    47  			path: "/xHOMEx/me/.local/share",
    48  		},
    49  		{
    50  			dir:  Data,
    51  			env:  env{"HOME": "/xHOMEx/me", "XDG_DATA_HOME": "relative/path"},
    52  			path: "/xHOMEx/me/.local/share",
    53  		},
    54  		{
    55  			dir:  Data,
    56  			env:  env{},
    57  			path: "",
    58  		},
    59  		{
    60  			dir:  Data,
    61  			env:  env{"HOME": "relative/path"},
    62  			path: "",
    63  		},
    64  		{
    65  			dir:  Config,
    66  			env:  env{"HOME": "/xHOMEx/me", "XDG_CONFIG_HOME": "/foo/config"},
    67  			path: "/foo/config",
    68  		},
    69  		{
    70  			dir:  Config,
    71  			env:  env{"HOME": "/xHOMEx/me"},
    72  			path: "/xHOMEx/me/.config",
    73  		},
    74  		{
    75  			dir:  Config,
    76  			env:  env{"HOME": "/xHOMEx/me", "XDG_CONFIG_HOME": "relative/path"},
    77  			path: "/xHOMEx/me/.config",
    78  		},
    79  		{
    80  			dir:  Config,
    81  			env:  env{},
    82  			path: "",
    83  		},
    84  		{
    85  			dir:  Cache,
    86  			env:  env{"HOME": "/xHOMEx/me", "XDG_CACHE_HOME": "/foo/cache"},
    87  			path: "/foo/cache",
    88  		},
    89  		{
    90  			dir:  Cache,
    91  			env:  env{"HOME": "/xHOMEx/me"},
    92  			path: "/xHOMEx/me/.cache",
    93  		},
    94  		{
    95  			dir:  Cache,
    96  			env:  env{"HOME": "/xHOMEx/me", "XDG_CACHE_HOME": "relative/path"},
    97  			path: "/xHOMEx/me/.cache",
    98  		},
    99  		{
   100  			dir:  Cache,
   101  			env:  env{},
   102  			path: "",
   103  		},
   104  		{
   105  			dir:  Runtime,
   106  			env:  env{"XDG_RUNTIME_DIR": secureDir},
   107  			path: secureDir,
   108  		},
   109  		{
   110  			dir:     Runtime,
   111  			env:     env{"XDG_RUNTIME_DIR": secureDir},
   112  			geteuid: func() int { return os.Geteuid() + 1 },
   113  			path:    "",
   114  		},
   115  		{
   116  			dir:  Runtime,
   117  			env:  env{"XDG_RUNTIME_DIR": readonlyDir},
   118  			path: "",
   119  		},
   120  		{
   121  			dir:  Runtime,
   122  			env:  env{"XDG_RUNTIME_DIR": allopenDir},
   123  			path: "",
   124  		},
   125  		{
   126  			dir:  Runtime,
   127  			env:  env{"HOME": secureDir},
   128  			path: "",
   129  		},
   130  		{
   131  			dir:  Runtime,
   132  			env:  env{},
   133  			path: "",
   134  		},
   135  	}
   136  	for _, test := range tests {
   137  		test.env.set()
   138  		if test.geteuid != nil {
   139  			geteuid = test.geteuid
   140  		} else {
   141  			geteuid = os.Geteuid
   142  		}
   143  		if path := test.dir.Path(); path != test.path {
   144  			var euidMod string
   145  			if test.geteuid != nil {
   146  				euidMod = " (euid modified)"
   147  			}
   148  			t.Errorf("In environment %v%s, %v.Path() = %q; want %q", test.env, euidMod, test.dir, path, test.path)
   149  		}
   150  	}
   151  }
   152  
   153  func TestDir_SearchPaths(t *testing.T) {
   154  	td := newTempDir(t)
   155  	defer td.cleanup()
   156  	allopenDir := td.mkdir("allopen", 0777)
   157  	secureDir := td.mkdir("secure", 0700)
   158  
   159  	tests := []struct {
   160  		dir   Dir
   161  		env   env
   162  		paths []string
   163  	}{
   164  		{
   165  			dir:   Data,
   166  			env:   env{},
   167  			paths: []string{"/usr/local/share", "/usr/share"},
   168  		},
   169  		{
   170  			dir:   Data,
   171  			env:   env{"HOME": "/xHOMEx/me"},
   172  			paths: []string{"/xHOMEx/me/.local/share", "/usr/local/share", "/usr/share"},
   173  		},
   174  		{
   175  			dir:   Data,
   176  			env:   env{"HOME": "/xHOMEx/me", "XDG_DATA_HOME": "/foo/data"},
   177  			paths: []string{"/foo/data", "/usr/local/share", "/usr/share"},
   178  		},
   179  		{
   180  			dir:   Data,
   181  			env:   env{"XDG_DATA_HOME": "/foo/data", "XDG_DATA_DIRS": "/mybacon/data"},
   182  			paths: []string{"/foo/data", "/mybacon/data"},
   183  		},
   184  		{
   185  			dir:   Data,
   186  			env:   env{"XDG_DATA_HOME": "/foo/data", "XDG_DATA_DIRS": "/mybacon/data:/eggs/data"},
   187  			paths: []string{"/foo/data", "/mybacon/data", "/eggs/data"},
   188  		},
   189  		{
   190  			dir:   Data,
   191  			env:   env{"XDG_DATA_HOME": "/foo/data", "XDG_DATA_DIRS": "/mybacon/data:/eggs/data:/woka/woka"},
   192  			paths: []string{"/foo/data", "/mybacon/data", "/eggs/data", "/woka/woka"},
   193  		},
   194  		{
   195  			dir:   Data,
   196  			env:   env{"XDG_DATA_HOME": "/foo/data", "XDG_DATA_DIRS": "/mybacon/data:relative/path:/woka/woka"},
   197  			paths: []string{"/foo/data", "/mybacon/data", "/woka/woka"},
   198  		},
   199  		{
   200  			dir:   Data,
   201  			env:   env{"XDG_DATA_HOME": "relative/path", "XDG_DATA_DIRS": "/mybacon/data:relative/path:/woka/woka"},
   202  			paths: []string{"/mybacon/data", "/woka/woka"},
   203  		},
   204  		{
   205  			dir:   Data,
   206  			env:   env{"XDG_DATA_DIRS": "/mybacon/data:/eggs/data:/woka/woka"},
   207  			paths: []string{"/mybacon/data", "/eggs/data", "/woka/woka"},
   208  		},
   209  		{
   210  			dir:   Config,
   211  			env:   env{"XDG_CONFIG_HOME": "/foo/config", "XDG_CONFIG_DIRS": "/mybacon/config:/eggs/config:/woka/woka"},
   212  			paths: []string{"/foo/config", "/mybacon/config", "/eggs/config", "/woka/woka"},
   213  		},
   214  		{
   215  			// Cache only has primary dir
   216  			dir:   Cache,
   217  			env:   env{"XDG_CACHE_HOME": "/foo/cache", "XDG_CACHE_DIRS": "/mybacon/config:/eggs/config:/woka/woka"},
   218  			paths: []string{"/foo/cache"},
   219  		},
   220  		{
   221  			dir:   Runtime,
   222  			env:   env{"XDG_RUNTIME_DIR": secureDir},
   223  			paths: []string{secureDir},
   224  		},
   225  		{
   226  			dir:   Runtime,
   227  			env:   env{"XDG_RUNTIME_DIR": allopenDir},
   228  			paths: []string{},
   229  		},
   230  	}
   231  	for _, test := range tests {
   232  		test.env.set()
   233  		paths := test.dir.SearchPaths()
   234  		if !stringsEqual(paths, test.paths) {
   235  			t.Errorf("In environment %v, %v.SearchPaths() = %q; want %q", test.env, test.dir, paths, test.paths)
   236  		}
   237  	}
   238  }
   239  
   240  func TestDir_Open(t *testing.T) {
   241  	td := newTempDir(t)
   242  	defer td.cleanup()
   243  	junkDir := td.mkdir("junk", 0777)
   244  	dir1 := td.mkdir("dir1", 0777)
   245  	dir2 := td.mkdir("dir2", 0777)
   246  	dir3 := td.mkdir("dir3", 0777)
   247  	td.newFile("dir1/foo.txt", "foo")
   248  	td.newFile("dir1/multiple.txt", "1")
   249  	td.newFile("dir2/bar.txt", "bar")
   250  	td.newFile("dir2/only2_3.txt", "this is 2")
   251  	td.newFile("dir2/multiple.txt", "2")
   252  	td.newFile("dir3/multiple.txt", "3")
   253  	td.newFile("dir3/only2_3.txt", "this is 3")
   254  
   255  	tests := []struct {
   256  		dir  Dir
   257  		env  env
   258  		name string
   259  
   260  		path string
   261  		err  bool
   262  	}{
   263  		{
   264  			dir:  Data,
   265  			env:  env{},
   266  			name: "foo.txt",
   267  			err:  true,
   268  		},
   269  		{
   270  			dir:  Data,
   271  			env:  env{"XDG_DATA_HOME": dir1, "XDG_DATA_DIRS": junkDir},
   272  			name: "foo.txt",
   273  			path: filepath.Join(dir1, "foo.txt"),
   274  		},
   275  		{
   276  			dir:  Data,
   277  			env:  env{"XDG_DATA_HOME": junkDir, "XDG_DATA_DIRS": junkDir},
   278  			name: "foo.txt",
   279  			err:  true,
   280  		},
   281  		{
   282  			dir:  Data,
   283  			env:  env{"XDG_DATA_HOME": dir1, "XDG_DATA_DIRS": dir2},
   284  			name: "foo.txt",
   285  			path: filepath.Join(dir1, "foo.txt"),
   286  		},
   287  		{
   288  			dir:  Data,
   289  			env:  env{"XDG_DATA_HOME": dir1, "XDG_DATA_DIRS": dir2},
   290  			name: "bar.txt",
   291  			path: filepath.Join(dir2, "bar.txt"),
   292  		},
   293  		{
   294  			dir:  Data,
   295  			env:  env{"XDG_DATA_HOME": dir1, "XDG_DATA_DIRS": dir2 + ":" + dir3},
   296  			name: "NOTREAL.txt",
   297  			err:  true,
   298  		},
   299  		{
   300  			dir:  Data,
   301  			env:  env{"XDG_DATA_HOME": dir1, "XDG_DATA_DIRS": dir2 + ":" + dir3},
   302  			name: "foo.txt",
   303  			path: filepath.Join(dir1, "foo.txt"),
   304  		},
   305  		{
   306  			dir:  Data,
   307  			env:  env{"XDG_DATA_HOME": dir1, "XDG_DATA_DIRS": dir2 + ":" + dir3},
   308  			name: "bar.txt",
   309  			path: filepath.Join(dir2, "bar.txt"),
   310  		},
   311  		{
   312  			dir:  Data,
   313  			env:  env{"XDG_DATA_HOME": dir1, "XDG_DATA_DIRS": dir2 + ":" + dir3},
   314  			name: "multiple.txt",
   315  			path: filepath.Join(dir1, "multiple.txt"),
   316  		},
   317  		{
   318  			dir:  Data,
   319  			env:  env{"XDG_DATA_HOME": dir1, "XDG_DATA_DIRS": dir2 + ":" + dir3},
   320  			name: "only2_3.txt",
   321  			path: filepath.Join(dir2, "only2_3.txt"),
   322  		},
   323  	}
   324  	for _, test := range tests {
   325  		test.env.set()
   326  		f, err := test.dir.Open(test.name)
   327  		switch {
   328  		case err == nil && test.err:
   329  			t.Errorf("In environment %v, %v.Open(%q) succeeded; want error", test.env, test.dir, test.name)
   330  		case err == nil && !test.err && f.Name() != test.path:
   331  			t.Errorf("In environment %v, %v.Open(%q).Name() = %q; want %q", test.env, test.dir, test.name, f.Name(), test.path)
   332  		case err != nil && !test.err:
   333  			t.Errorf("In environment %v, %v.Open(%q) error: %v", test.env, test.dir, test.name, err)
   334  		}
   335  		if f != nil {
   336  			f.Close()
   337  		}
   338  	}
   339  }
   340  
   341  func TestDir_Create(t *testing.T) {
   342  	td := newTempDir(t)
   343  	defer td.cleanup()
   344  	junkDir := td.mkdir("junk", 0777)
   345  	dataDir := td.mkdir("data", 0777)
   346  
   347  	tests := []struct {
   348  		dir  Dir
   349  		env  env
   350  		name string
   351  
   352  		path       string
   353  		err        bool
   354  		permChecks []permCheck
   355  	}{
   356  		{
   357  			dir:  Data,
   358  			env:  env{"XDG_DATA_HOME": dataDir, "XDG_DATA_DIRS": junkDir},
   359  			name: "foo01",
   360  			path: filepath.Join(dataDir, "foo01"),
   361  		},
   362  		{
   363  			dir:  Data,
   364  			env:  env{},
   365  			name: "foo02",
   366  			err:  true,
   367  		},
   368  		{
   369  			dir:  Data,
   370  			env:  env{"XDG_DATA_HOME": dataDir, "XDG_DATA_DIRS": junkDir},
   371  			name: filepath.Join("foo03", "bar"),
   372  			path: filepath.Join(dataDir, "foo03", "bar"),
   373  			permChecks: []permCheck{
   374  				{filepath.Join(dataDir, "foo03"), 0700},
   375  			},
   376  		},
   377  		{
   378  			dir:  Data,
   379  			env:  env{"XDG_DATA_HOME": filepath.Join(td.dir, "NOTREAL"), "XDG_DATA_DIRS": junkDir},
   380  			name: filepath.Join("foo04", "bar"),
   381  			path: filepath.Join(td.dir, "NOTREAL", "foo04", "bar"),
   382  			permChecks: []permCheck{
   383  				{filepath.Join(td.dir, "NOTREAL"), 0700},
   384  				{filepath.Join(td.dir, "NOTREAL", "foo04"), 0700},
   385  			},
   386  		},
   387  	}
   388  	for _, test := range tests {
   389  		test.env.set()
   390  		f, err := test.dir.Create(test.name)
   391  		switch {
   392  		case err == nil && test.err:
   393  			t.Errorf("In environment %v, %v.Create(%q) succeeded; want error", test.env, test.dir, test.name)
   394  		case err == nil && !test.err && f.Name() != test.path:
   395  			t.Errorf("In environment %v, %v.Create(%q).Name() = %q; want %q", test.env, test.dir, test.name, f.Name(), test.path)
   396  		case err != nil && !test.err:
   397  			t.Errorf("In environment %v, %v.Create(%q) error: %v", test.env, test.dir, test.name, err)
   398  		}
   399  		if f != nil {
   400  			f.Close()
   401  		}
   402  		for _, pc := range test.permChecks {
   403  			info, err := os.Stat(pc.name)
   404  			if err != nil {
   405  				t.Errorf("In environment %v, %v.Create(%q): stat %s error: %v", test.env, test.dir, test.name, pc.name, err)
   406  				continue
   407  			}
   408  			if perm := info.Mode().Perm(); perm != pc.perm {
   409  				t.Errorf("In environment %v, %v.Create(%q): %s has permission %v; want %v", test.env, test.dir, test.name, pc.name, perm, pc.perm)
   410  			}
   411  		}
   412  	}
   413  }
   414  
   415  func stringsEqual(a, b []string) bool {
   416  	if len(a) != len(b) {
   417  		return false
   418  	}
   419  	for i := range a {
   420  		if a[i] != b[i] {
   421  			return false
   422  		}
   423  	}
   424  	return true
   425  }
   426  
   427  type tempDir struct {
   428  	t   *testing.T
   429  	dir string
   430  }
   431  
   432  func newTempDir(t *testing.T) *tempDir {
   433  	td := &tempDir{t: t}
   434  	var err error
   435  	td.dir, err = ioutil.TempDir("", "xdgdir_test")
   436  	if err != nil {
   437  		t.Fatal("making temp dir:", err)
   438  	}
   439  	return td
   440  }
   441  
   442  // newFile creates a file and returns its path.
   443  func (td *tempDir) newFile(name string, data string) string {
   444  	path := filepath.Join(td.dir, name)
   445  	f, err := os.Create(path)
   446  	if err != nil {
   447  		td.t.Fatalf("newFile(%q, %q) error: %v", name, data, err)
   448  	}
   449  	_, werr := f.Write([]byte(data))
   450  	cerr := f.Close()
   451  	if werr != nil {
   452  		td.t.Errorf("newFile(%q, %q) write error: %v", name, data, err)
   453  	}
   454  	if cerr != nil {
   455  		td.t.Errorf("newFile(%q, %q) close error: %v", name, data, err)
   456  	}
   457  	if werr != nil || cerr != nil {
   458  		td.t.FailNow()
   459  	}
   460  	return path
   461  }
   462  
   463  // mkdir creates a directory and returns its path.
   464  func (td *tempDir) mkdir(name string, perm os.FileMode) string {
   465  	path := filepath.Join(td.dir, name)
   466  	err := os.Mkdir(path, perm)
   467  	if err != nil {
   468  		td.t.Fatal(err)
   469  	}
   470  	return path
   471  }
   472  
   473  func (td *tempDir) cleanup() {
   474  	err := os.RemoveAll(td.dir)
   475  	if err != nil {
   476  		td.t.Log("failed to clean up temp dir:", err)
   477  	}
   478  }
   479  
   480  type permCheck struct {
   481  	name string
   482  	perm os.FileMode
   483  }
   484  
   485  type env map[string]string
   486  
   487  func (e env) set() {
   488  	getenv = func(key string) string {
   489  		return e[key]
   490  	}
   491  	findHome = func() string {
   492  		return e["HOME"]
   493  	}
   494  }