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 }