github.com/decred/politeia@v1.4.0/politeiad/backend/gitbe/git_test.go (about)

     1  // Copyright (c) 2017-2019 The Decred developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package gitbe
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"compress/zlib"
    11  	"fmt"
    12  	"io"
    13  	"os"
    14  	"path/filepath"
    15  	"strings"
    16  	"testing"
    17  
    18  	"github.com/decred/slog"
    19  )
    20  
    21  type testWriter struct {
    22  	t *testing.T
    23  }
    24  
    25  func (w *testWriter) Write(p []byte) (int, error) {
    26  	w.t.Logf("%s", p)
    27  	return len(p), nil
    28  }
    29  
    30  func newGitBackEnd() *gitBackEnd {
    31  	dir, err := os.MkdirTemp("", "politeiad.test")
    32  	if err != nil {
    33  		panic(fmt.Sprintf("%v", err))
    34  	}
    35  	return &gitBackEnd{
    36  		root:     dir,
    37  		gitPath:  "git", // assume installed
    38  		gitTrace: true,
    39  	}
    40  }
    41  
    42  func TestVersion(t *testing.T) {
    43  	g := newGitBackEnd()
    44  	defer os.RemoveAll(g.root)
    45  
    46  	_, err := g.gitVersion()
    47  	if err != nil {
    48  		t.Fatal(err)
    49  	}
    50  }
    51  
    52  func TestInit(t *testing.T) {
    53  	log := slog.NewBackend(&testWriter{t}).Logger("TEST")
    54  	UseLogger(log)
    55  	g := newGitBackEnd()
    56  	defer os.RemoveAll(g.root)
    57  
    58  	err := g.gitInitRepo(g.root, defaultRepoConfig)
    59  	if err != nil {
    60  		t.Fatal(err)
    61  	}
    62  }
    63  
    64  func TestLog(t *testing.T) {
    65  	log := slog.NewBackend(&testWriter{t}).Logger("TEST")
    66  	UseLogger(log)
    67  	g := newGitBackEnd()
    68  	defer os.RemoveAll(g.root)
    69  
    70  	_, err := g.gitInit(g.root)
    71  	if err != nil {
    72  		t.Fatal(err)
    73  	}
    74  
    75  	_, err = g.gitLog(g.root)
    76  	if err == nil {
    77  		t.Fatal("empty repo should fail log")
    78  	}
    79  }
    80  
    81  func TestFsck(t *testing.T) {
    82  	// Test git fsck, we build on top of that with a dcrtime fsck
    83  	log := slog.NewBackend(&testWriter{t}).Logger("TEST")
    84  	UseLogger(log)
    85  	g := newGitBackEnd()
    86  	defer os.RemoveAll(g.root)
    87  
    88  	// Init git repo
    89  	err := g.gitInitRepo(g.root, defaultRepoConfig)
    90  	if err != nil {
    91  		t.Fatal(err)
    92  	}
    93  
    94  	// Create a file in repo
    95  	tf := filepath.Join(g.root, "testfile")
    96  	t.Logf("fsck location: %v", tf)
    97  	err = os.WriteFile(tf, []byte("this is a test\n"), 0644)
    98  	if err != nil {
    99  		t.Fatal(err)
   100  	}
   101  
   102  	// Git add file
   103  	err = g.gitAdd(g.root, tf)
   104  	if err != nil {
   105  		t.Fatal(err)
   106  	}
   107  
   108  	// Git commit
   109  	err = g.gitCommit(g.root, "Add testfile")
   110  	if err != nil {
   111  		t.Fatal(err)
   112  	}
   113  
   114  	// First mess up refs by reading file in memry and then corrupting it
   115  	masterFilename := filepath.Join(g.root, ".git/refs/heads/master")
   116  	master, err := os.ReadFile(masterFilename)
   117  	if err != nil {
   118  		t.Fatal(err)
   119  	}
   120  	// Corrupt master
   121  	masterCorrupt := make([]byte, len(master))
   122  	for k := range masterCorrupt {
   123  		masterCorrupt[k] = '0'
   124  	}
   125  	err = os.WriteFile(masterFilename, masterCorrupt, 0644)
   126  	if err != nil {
   127  		t.Fatal(err)
   128  	}
   129  
   130  	// Expect fsck to fail
   131  	_, err = g.gitFsck(g.root)
   132  	if err == nil {
   133  		t.Fatalf("expected fsck error")
   134  	}
   135  
   136  	// Restore master
   137  	err = os.WriteFile(masterFilename, master, 0644)
   138  	if err != nil {
   139  		t.Fatal(err)
   140  	}
   141  
   142  	// Expect fsck to work again
   143  	_, err = g.gitFsck(g.root)
   144  	if err != nil {
   145  		t.Fatal(err)
   146  	}
   147  
   148  	// Use git cat-file to fish out the types and values from objects.
   149  
   150  	// First find the last commit
   151  	out, err := g.git(g.root, "log", "--pretty=oneline")
   152  	if err != nil {
   153  		t.Fatal(err)
   154  	}
   155  	s := strings.SplitN(out[0], " ", 2)
   156  	comitHash := s[0]
   157  
   158  	// Get type
   159  	out, err = g.git(g.root, "cat-file", "-t", comitHash)
   160  	if err != nil {
   161  		t.Fatal(err)
   162  	}
   163  	if out[0] != "commit" {
   164  		t.Fatalf("invalid type: %v", out[0])
   165  	}
   166  
   167  	// Now get the tree object
   168  	out, err = g.git(g.root, "cat-file", "-p", comitHash)
   169  	if err != nil {
   170  		t.Fatal(err)
   171  	}
   172  	s = strings.SplitN(out[0], " ", 2)
   173  	treeHash := s[1]
   174  
   175  	out, err = g.git(g.root, "cat-file", "-t", treeHash)
   176  	if err != nil {
   177  		t.Fatal(err)
   178  	}
   179  	if out[0] != "tree" {
   180  		t.Fatalf("invalid type: %v", out[0])
   181  	}
   182  
   183  	// Now go get the blob
   184  	out, err = g.git(g.root, "cat-file", "-p", treeHash)
   185  	if err != nil {
   186  		t.Fatal(err)
   187  	}
   188  	// out = 100644 blob 90bfcb510602aa11ae53a42dcec18ea39fbd8cec\ttestfile
   189  	s = strings.Split(out[0], "\t")
   190  	s = strings.Split(s[0], " ")
   191  	blobHash := s[2]
   192  
   193  	out, err = g.git(g.root, "cat-file", "-t", blobHash)
   194  	if err != nil {
   195  		t.Fatal(err)
   196  	}
   197  	if out[0] != "blob" {
   198  		t.Fatalf("invalid type: %v", out[0])
   199  	}
   200  
   201  	// Now we corrupt the blob object
   202  	blobObjectFilename := filepath.Join(g.root, ".git", "objects",
   203  		blobHash[:2], blobHash[2:])
   204  	blobObject, err := os.ReadFile(blobObjectFilename)
   205  	if err != nil {
   206  		t.Fatal(err)
   207  	}
   208  	// Make a copy to uncorrupt later
   209  	xxx := make([]byte, len(blobObject))
   210  	copy(xxx, blobObject)
   211  
   212  	// Uncompress
   213  	b := bytes.NewBuffer(xxx)
   214  	r, err := zlib.NewReader(b)
   215  	if err != nil {
   216  		t.Fatal(err)
   217  	}
   218  	buf := new(bytes.Buffer)
   219  	w := bufio.NewWriter(buf)
   220  	io.Copy(w, r)
   221  	r.Close()
   222  	// buf now contains a header + the text we shoved in it.  We are going
   223  	// to use this later to corrupt the file by uppercasing the last
   224  	// letter.
   225  
   226  	// Make file writable
   227  	err = os.Chmod(blobObjectFilename, 0644)
   228  	if err != nil {
   229  		t.Fatal(err)
   230  	}
   231  	location := len(blobObject) - 2 // zlib error
   232  	blobObject[location] = ^blobObject[location]
   233  	err = os.WriteFile(blobObjectFilename, blobObject, 0644)
   234  	if err != nil {
   235  		t.Fatal(err)
   236  	}
   237  
   238  	// Expect fsck to fail
   239  	_, err = g.gitFsck(g.root)
   240  	if err == nil {
   241  		t.Fatalf("expected fsck error")
   242  	}
   243  
   244  	// Now write buf back with capitalized letter at the end
   245  	copy(blobObject, xxx) // restore blob object
   246  	corruptBuf := buf.Bytes()
   247  	location = len(corruptBuf) - 2 // account for \n
   248  	corruptBuf[location] &= 0xdf
   249  
   250  	var bc bytes.Buffer
   251  	w2, err := zlib.NewWriterLevel(&bc, 1) // git uses zlib level 1
   252  	if err != nil {
   253  		t.Fatal(err)
   254  	}
   255  	w2.Write(corruptBuf)
   256  	w2.Close()
   257  
   258  	// Write corrupt zlib data
   259  	err = os.WriteFile(blobObjectFilename, bc.Bytes(), 0644)
   260  	if err != nil {
   261  		t.Fatal(err)
   262  	}
   263  
   264  	// Expect fsck to fail
   265  	_, err = g.gitFsck(g.root)
   266  	if err == nil {
   267  		t.Fatalf("expected fsck error")
   268  	}
   269  
   270  	// Restore object
   271  	err = os.WriteFile(blobObjectFilename, xxx, 0644)
   272  	if err != nil {
   273  		t.Fatal(err)
   274  	}
   275  
   276  	// Expect fsck to fail
   277  	_, err = g.gitFsck(g.root)
   278  	if err != nil {
   279  		t.Fatal(err)
   280  	}
   281  }