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  }