cuelang.org/go@v0.10.1/internal/vcs/vcs_test.go (about) 1 // Copyright 2024 CUE Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package vcs 16 17 import ( 18 "context" 19 "os" 20 "os/exec" 21 "path/filepath" 22 "testing" 23 "time" 24 25 "github.com/go-quicktest/qt" 26 "golang.org/x/tools/txtar" 27 ) 28 29 func TestGit(t *testing.T) { 30 skipIfNoExecutable(t, "git") 31 ctx := context.Background() 32 dir := t.TempDir() 33 34 testFS, err := txtar.FS(txtar.Parse([]byte(` 35 -- subdir/foo -- 36 -- subdir/bar/baz -- 37 -- bar.txt -- 38 -- baz/something -- 39 `))) 40 qt.Assert(t, qt.IsNil(err)) 41 err = copyFS(dir, testFS) 42 qt.Assert(t, qt.IsNil(err)) 43 44 // In the tests that follow, we are testing the scenario where a module is 45 // present in $dir/subdir (the VCS is rooted at $dir). cue/load or similar 46 // would establish the absolute path $dir/subdir is the CUE module root, and 47 // as such we use that absolute path as an argument in the calls to the VCS 48 // implementation. 49 subdir := filepath.Join(dir, "subdir") 50 51 _, err = New("git", subdir) 52 qt.Assert(t, qt.ErrorMatches(err, `git VCS not found in any parent of ".+"`)) 53 54 InitTestEnv(t) 55 mustRunCmd(t, dir, "git", "init") 56 v, err := New("git", subdir) 57 qt.Assert(t, qt.IsNil(err)) 58 59 // The status shows that we have uncommitted files 60 // because we haven't yet added the files after doing 61 // git init. 62 statusuncommitted, err := v.Status(ctx, subdir) 63 qt.Assert(t, qt.IsNil(err)) 64 qt.Assert(t, qt.IsTrue(statusuncommitted.Uncommitted)) 65 66 mustRunCmd(t, dir, "git", "add", ".") 67 statusuncommitted, err = v.Status(ctx, subdir) 68 qt.Assert(t, qt.IsNil(err)) 69 qt.Assert(t, qt.IsTrue(statusuncommitted.Uncommitted)) 70 71 commitTime := time.Now().Truncate(time.Second) 72 mustRunCmd(t, dir, "git", 73 "-c", "user.email=cueckoo@gmail.com", 74 "-c", "user.name=cueckoo", 75 "commit", "-m", "something", 76 ) 77 status, err := v.Status(ctx, subdir) 78 qt.Assert(t, qt.IsNil(err)) 79 qt.Assert(t, qt.IsFalse(status.Uncommitted)) 80 qt.Assert(t, qt.IsTrue(!status.CommitTime.Before(commitTime))) 81 qt.Assert(t, qt.Matches(status.Revision, `[0-9a-f]+`)) 82 83 // Test various permutations of ListFiles 84 var files []string 85 allFiles := []string{ 86 "bar.txt", 87 "baz/something", 88 "subdir/bar/baz", 89 "subdir/foo", 90 } 91 92 // Empty dir implies repo root, i.e. all files 93 files, err = v.ListFiles(ctx, "") 94 qt.Assert(t, qt.IsNil(err)) 95 qt.Assert(t, qt.DeepEquals(files, allFiles)) 96 97 // Explicit repo root 98 files, err = v.ListFiles(ctx, dir) 99 qt.Assert(t, qt.IsNil(err)) 100 qt.Assert(t, qt.DeepEquals(files, allFiles)) 101 102 // Relative path file under repo root 103 files, err = v.ListFiles(ctx, dir, "bar.txt") 104 qt.Assert(t, qt.IsNil(err)) 105 qt.Assert(t, qt.DeepEquals(files, []string{"bar.txt"})) 106 107 // Absolute path file under repo root 108 files, err = v.ListFiles(ctx, dir, filepath.Join(dir, "bar.txt")) 109 qt.Assert(t, qt.IsNil(err)) 110 qt.Assert(t, qt.DeepEquals(files, []string{"bar.txt"})) 111 112 // Relative path sub directory listed from root 113 files, err = v.ListFiles(ctx, dir, "subdir") 114 qt.Assert(t, qt.IsNil(err)) 115 qt.Assert(t, qt.DeepEquals(files, []string{ 116 "subdir/bar/baz", 117 "subdir/foo", 118 })) 119 120 // Absolute path sub directory listed from root 121 files, err = v.ListFiles(ctx, dir, filepath.Join(dir, "subdir")) 122 qt.Assert(t, qt.IsNil(err)) 123 qt.Assert(t, qt.DeepEquals(files, []string{ 124 "subdir/bar/baz", 125 "subdir/foo", 126 })) 127 128 // Listing of files in sub directory 129 files, err = v.ListFiles(ctx, subdir) 130 qt.Assert(t, qt.IsNil(err)) 131 qt.Assert(t, qt.DeepEquals(files, []string{ 132 "bar/baz", 133 "foo", 134 })) 135 136 // Change a file that's not in subdir. The status in subdir should remain 137 // the same. 138 err = os.WriteFile(filepath.Join(dir, "bar.txt"), []byte("something else"), 0o666) 139 qt.Assert(t, qt.IsNil(err)) 140 statuschanged, err := v.Status(ctx) 141 qt.Assert(t, qt.IsNil(err)) 142 qt.Assert(t, qt.IsTrue(statuschanged.Uncommitted)) 143 status1, err := v.Status(ctx, subdir) 144 qt.Assert(t, qt.IsNil(err)) 145 qt.Assert(t, qt.DeepEquals(status1, status)) 146 147 // Restore the file and ensure Status is clean 148 err = os.WriteFile(filepath.Join(dir, "bar.txt"), nil, 0o666) 149 qt.Assert(t, qt.IsNil(err)) 150 files, err = v.ListFiles(ctx, dir) 151 qt.Assert(t, qt.IsNil(err)) 152 qt.Assert(t, qt.DeepEquals(files, allFiles)) 153 status2, err := v.Status(ctx) 154 qt.Assert(t, qt.IsNil(err)) 155 qt.Assert(t, qt.DeepEquals(status2, status)) 156 157 // Add an untracked file 158 untracked := filepath.Join(dir, "untracked") 159 err = os.WriteFile(untracked, nil, 0666) 160 qt.Assert(t, qt.IsNil(err)) 161 files, err = v.ListFiles(ctx, dir) // Does not include untracked file 162 qt.Assert(t, qt.IsNil(err)) 163 qt.Assert(t, qt.DeepEquals(files, allFiles)) 164 statusuntracked, err := v.Status(ctx) // Status does now show uncommitted changes 165 qt.Assert(t, qt.IsNil(err)) 166 qt.Assert(t, qt.IsTrue(statusuntracked.Uncommitted)) 167 168 // Remove the untracked file and ensure Status is clean 169 err = os.Remove(untracked) 170 qt.Assert(t, qt.IsNil(err)) 171 files, err = v.ListFiles(ctx, dir) 172 qt.Assert(t, qt.IsNil(err)) 173 qt.Assert(t, qt.DeepEquals(files, allFiles)) 174 status3, err := v.Status(ctx) 175 qt.Assert(t, qt.IsNil(err)) 176 qt.Assert(t, qt.DeepEquals(status3, status)) 177 178 // // Remove a tracked file so that it is now "missing" 179 err = os.Remove(filepath.Join(dir, "bar.txt")) 180 qt.Assert(t, qt.IsNil(err)) 181 files, err = v.ListFiles(ctx, dir) // Still reports "missing" file 182 qt.Assert(t, qt.IsNil(err)) 183 qt.Assert(t, qt.DeepEquals(files, allFiles)) 184 statusmissing, err := v.Status(ctx) // Status does now show uncommitted changes 185 qt.Assert(t, qt.IsNil(err)) 186 qt.Assert(t, qt.IsTrue(statusmissing.Uncommitted)) 187 } 188 189 func mustRunCmd(t *testing.T, dir string, exe string, args ...string) { 190 c := exec.Command(exe, args...) 191 c.Dir = dir 192 data, err := c.CombinedOutput() 193 qt.Assert(t, qt.IsNil(err), qt.Commentf("output: %q", data)) 194 } 195 196 func skipIfNoExecutable(t *testing.T, exeName string) { 197 if _, err := exec.LookPath(exeName); err != nil { 198 t.Skipf("cannot find %q executable: %v", exeName, err) 199 } 200 }