github.com/atlassian/git-lob@v0.0.0-20150806085256-2386a5ed291a/core/storage_test.go (about)

     1  package core
     2  
     3  import (
     4  	"crypto/sha1"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path"
     9  	"path/filepath"
    10  
    11  	. "github.com/atlassian/git-lob/Godeps/_workspace/src/github.com/onsi/ginkgo"
    12  	. "github.com/atlassian/git-lob/Godeps/_workspace/src/github.com/onsi/gomega"
    13  	. "github.com/atlassian/git-lob/util"
    14  )
    15  
    16  var _ = Describe("Storage", func() {
    17  
    18  	root := filepath.Join(os.TempDir(), "StorageTest")
    19  	separateGitDir := filepath.Join(os.TempDir(), "StorageTestGitDir")
    20  	sharedStore := filepath.Join(os.TempDir(), "StorageTest_SharedStore")
    21  	folders := []string{
    22  		filepath.Join(root, "folder1"),
    23  		filepath.Join(root, "folder2"),
    24  		filepath.Join(root, "folder3"),
    25  		filepath.Join(root, "folder1", "sub1"),
    26  		filepath.Join(root, "folder1", "sub2"),
    27  		filepath.Join(root, "folder1", "sub1", "subsub1"),
    28  		filepath.Join(root, "folder1", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l")}
    29  
    30  	Describe("Identifying git repo root", func() {
    31  		Context("Valid git repo", func() {
    32  			var oldwd string
    33  			BeforeEach(func() {
    34  				oldwd, _ = os.Getwd()
    35  				// Set up git repo with some subfolders
    36  				CreateGitRepoForTest(root)
    37  
    38  				for _, f := range folders {
    39  					err := os.MkdirAll(f, 0755)
    40  					if err != nil {
    41  						fmt.Printf("Can't MkdirAll %v: %v", f, err)
    42  					}
    43  				}
    44  
    45  			})
    46  
    47  			AfterEach(func() {
    48  				// Delete repo
    49  				err := ForceRemoveAll(root)
    50  				if err != nil {
    51  					Fail(err.Error())
    52  				}
    53  				os.Chdir(oldwd)
    54  			})
    55  
    56  			It("finds root git folder", func() {
    57  
    58  				// Need to expand root for symlinks here in order to guarantee string comparison works
    59  				// /var turns into /private/var on OS X for example
    60  				// Can't use this for creating repos etc though, OS X doesn't like direct access
    61  				expandedroot, _ := filepath.EvalSymlinks(root)
    62  				for _, f := range folders {
    63  					err := os.Chdir(f)
    64  					if err != nil {
    65  						Fail(fmt.Sprintf("Can't chdir to %v: %v", f, err))
    66  					}
    67  					testroot, sep, err := GetRepoRoot()
    68  					Expect(err).To(BeNil(), "Should be no error getting repo root")
    69  					Expect(testroot).To(Equal(expandedroot))
    70  					Expect(sep).To(Equal(false))
    71  				}
    72  			})
    73  		})
    74  		Context("Invalid git repo", func() {
    75  			var oldwd string
    76  			BeforeEach(func() {
    77  				oldwd, _ = os.Getwd()
    78  			})
    79  			AfterEach(func() {
    80  				os.Chdir(oldwd)
    81  			})
    82  			It("Fails safely outside a git repo", func() {
    83  				// Relies on temp dir not being a git repo, which should be valid assumption
    84  				os.Chdir(os.TempDir())
    85  				testroot, _, err := GetRepoRoot()
    86  				Expect(testroot).To(Equal(""))
    87  				Expect(err).ToNot(BeNil(), "Should be error outside git repo")
    88  			})
    89  
    90  		})
    91  
    92  	})
    93  
    94  	Describe("Finding git dir", func() {
    95  		Context("Git repo with standard git dir", func() {
    96  			var oldwd string
    97  			BeforeEach(func() {
    98  				oldwd, _ = os.Getwd()
    99  				// Set up git repo with some subfolders
   100  				CreateGitRepoForTest(root)
   101  
   102  				for _, f := range folders {
   103  					err := os.MkdirAll(f, 0755)
   104  					if err != nil {
   105  						fmt.Printf("Can't MkdirAll %v: %v", f, err)
   106  					}
   107  				}
   108  
   109  			})
   110  
   111  			AfterEach(func() {
   112  				os.Chdir(oldwd)
   113  				// Delete repo
   114  				err := ForceRemoveAll(root)
   115  				if err != nil {
   116  					Fail(err.Error())
   117  				}
   118  			})
   119  
   120  			It("finds git dir", func() {
   121  
   122  				// Need to expand root for symlinks here in order to guarantee string comparison works
   123  				// /var turns into /private/var on OS X for example
   124  				// Can't use this for creating repos etc though, OS X doesn't like direct access
   125  				gitdir, _ := filepath.EvalSymlinks(path.Join(root, ".git"))
   126  
   127  				for _, f := range folders {
   128  					err := os.Chdir(f)
   129  					if err != nil {
   130  						Fail(fmt.Sprintf("Can't chdir to %v: %v", f, err))
   131  					}
   132  					testgitdir := GetGitDir()
   133  					Expect(testgitdir).To(Equal(gitdir))
   134  				}
   135  			})
   136  
   137  		})
   138  
   139  		Context("Git repo with separate git dir", func() {
   140  			var oldwd string
   141  			BeforeEach(func() {
   142  				oldwd, _ = os.Getwd()
   143  				// Set up git repo with some subfolders
   144  				CreateGitRepoWithSeparateGitDirForTest(root, separateGitDir)
   145  
   146  				for _, f := range folders {
   147  					err := os.MkdirAll(f, 0755)
   148  					if err != nil {
   149  						fmt.Printf("Can't MkdirAll %v: %v", f, err)
   150  					}
   151  				}
   152  
   153  			})
   154  
   155  			AfterEach(func() {
   156  				os.Chdir(oldwd)
   157  				// Delete repo
   158  				err := ForceRemoveAll(root)
   159  				if err != nil {
   160  					Fail(err.Error())
   161  				}
   162  			})
   163  
   164  			It("finds git dir", func() {
   165  
   166  				// Need to expand root for symlinks here in order to guarantee string comparison works
   167  				// /var turns into /private/var on OS X for example
   168  				// Can't use this for creating repos etc though, OS X doesn't like direct access
   169  				gitdir, _ := filepath.EvalSymlinks(separateGitDir)
   170  
   171  				for _, f := range folders {
   172  					err := os.Chdir(f)
   173  					if err != nil {
   174  						Fail(fmt.Sprintf("Can't chdir to %v: %v", f, err))
   175  					}
   176  					testgitdir := GetGitDir()
   177  					Expect(testgitdir).To(Equal(gitdir))
   178  				}
   179  			})
   180  
   181  		})
   182  	})
   183  
   184  	Describe("Storing a LOB", func() {
   185  		// Common git repo
   186  		var oldwd string
   187  		BeforeEach(func() {
   188  			oldwd, _ = os.Getwd()
   189  			// Set up git repo with some subfolders
   190  			CreateGitRepoForTest(root)
   191  
   192  			for _, f := range folders {
   193  				err := os.MkdirAll(f, 0755)
   194  				if err != nil {
   195  					fmt.Printf("Can't MkdirAll %v: %v", f, err)
   196  				}
   197  			}
   198  			os.Chdir(root)
   199  		})
   200  
   201  		AfterEach(func() {
   202  			os.Chdir(oldwd)
   203  			// Delete repo
   204  			err := ForceRemoveAll(root)
   205  			if err != nil {
   206  				Fail(err.Error())
   207  			}
   208  		})
   209  
   210  		Context("Store small single chunk LOB", func() {
   211  			testFileName := path.Join(folders[2], "small.dat")
   212  			var correctLOBInfo *LOBInfo
   213  
   214  			BeforeEach(func() {
   215  				correctLOBInfo = CreateSmallTestLOBFileForStoring(testFileName)
   216  			})
   217  			AfterEach(func() {
   218  				os.Remove(testFileName)
   219  			})
   220  
   221  			It("correctly stores a small file", func() {
   222  				f, err := os.Open(testFileName)
   223  				if err != nil {
   224  					Fail(fmt.Sprintf("Can't reopen test file %v: %v", testFileName, err))
   225  				}
   226  				defer f.Close()
   227  				// Need to read leader for consistency with real usage
   228  				leader := make([]byte, SHALineLen)
   229  				c, err := f.Read(leader)
   230  				if err != nil {
   231  					Fail(fmt.Sprintf("Can't read leader of test file %v: %v", testFileName, err))
   232  				}
   233  				lobinfo, err := StoreLOB(f, leader[:c])
   234  				Expect(err).To(BeNil(), "Shouldn't be error storing LOB")
   235  				Expect(lobinfo).To(Equal(correctLOBInfo))
   236  				fileinfo, err := os.Stat(GetLocalLOBChunkPath(lobinfo.SHA, 0))
   237  				Expect(err).To(BeNil(), "Shouldn't be error opening stored LOB")
   238  				Expect(fileinfo.Size()).To(Equal(lobinfo.Size), "Stored LOB should be correct size")
   239  			})
   240  
   241  		})
   242  		Context("Store zero size file", func() {
   243  			zerofile := path.Join(folders[1], "zero.dat")
   244  			BeforeEach(func() {
   245  				CreateRandomFileForTest(0, zerofile)
   246  			})
   247  			AfterEach(func() {
   248  				os.Remove(zerofile)
   249  			})
   250  
   251  			It("correctly stores zero size files", func() {
   252  				f, err := os.Open(zerofile)
   253  				if err != nil {
   254  					Fail(fmt.Sprintf("Can't reopen test file %v: %v", zerofile, err))
   255  				}
   256  				defer f.Close()
   257  				// Need TRY to read leader for consistency with real usage
   258  				leader := make([]byte, SHALineLen)
   259  				c, err := f.Read(leader)
   260  				lobinfo, err := StoreLOB(f, leader[:c])
   261  				Expect(err).To(BeNil(), "Shouldn't be error storing LOB")
   262  				Expect(lobinfo.Size).To(BeEquivalentTo(0), "Size should be correct")
   263  				Expect(lobinfo.NumChunks).To(BeEquivalentTo(0), "Should only be one chunk")
   264  				shaZero := sha1.New()
   265  				shaZeroStr := fmt.Sprintf("%x", string(shaZero.Sum(nil)))
   266  				Expect(lobinfo.SHA).To(Equal(shaZeroStr), "SHA should be the zero file content SHA")
   267  
   268  			})
   269  		})
   270  
   271  		Context("Store single chunk LOB of exact chunk size", func() {
   272  			exact1 := path.Join(folders[1], "exact1.dat")
   273  			exact2 := path.Join(folders[1], "exact2.dat")
   274  			var oldChunkSize int64
   275  
   276  			BeforeEach(func() {
   277  				// Jig the chunk size for efficient testing
   278  				oldChunkSize = ChunkSize
   279  				ChunkSize = 200
   280  				CreateRandomFileForTest(ChunkSize, exact1)
   281  				CreateRandomFileForTest(ChunkSize*2, exact2)
   282  
   283  			})
   284  			AfterEach(func() {
   285  				os.Remove(exact1)
   286  				os.Remove(exact2)
   287  				ChunkSize = oldChunkSize
   288  			})
   289  
   290  			It("correctly stores files which are exact multiples of chunk size", func() {
   291  				f, err := os.Open(exact1)
   292  				if err != nil {
   293  					Fail(fmt.Sprintf("Can't reopen test file %v: %v", exact1, err))
   294  				}
   295  				defer f.Close()
   296  				// Need to read leader for consistency with real usage
   297  				leader := make([]byte, SHALineLen)
   298  				c, err := f.Read(leader)
   299  				if err != nil {
   300  					Fail(fmt.Sprintf("Can't read leader of test file %v: %v", exact1, err))
   301  				}
   302  				lobinfo, err := StoreLOB(f, leader[:c])
   303  				Expect(err).To(BeNil(), "Shouldn't be error storing LOB")
   304  				Expect(lobinfo.Size).To(BeEquivalentTo(ChunkSize), "Size should be correct")
   305  				Expect(lobinfo.NumChunks).To(BeEquivalentTo(1), "Should only be one chunk")
   306  				fileinfo, err := os.Stat(GetLocalLOBChunkPath(lobinfo.SHA, 0))
   307  				Expect(err).To(BeNil(), "Shouldn't be error opening stored LOB")
   308  				Expect(fileinfo.Size()).To(Equal(lobinfo.Size), "Stored LOB should be correct size")
   309  
   310  				f2, err := os.Open(exact2)
   311  				if err != nil {
   312  					Fail(fmt.Sprintf("Can't reopen test file %v: %v", exact2, err))
   313  				}
   314  				defer f2.Close()
   315  				// Need to read leader for consistency with real usage
   316  				leader = make([]byte, SHALineLen)
   317  				c, err = f2.Read(leader)
   318  				if err != nil {
   319  					Fail(fmt.Sprintf("Can't read leader of test file %v: %v", exact2, err))
   320  				}
   321  				lobinfo, err = StoreLOB(f2, leader[:c])
   322  				Expect(err).To(BeNil(), "Shouldn't be error storing LOB")
   323  				Expect(lobinfo.Size).To(BeEquivalentTo(ChunkSize*2), "Size should be correct")
   324  				Expect(lobinfo.NumChunks).To(BeEquivalentTo(2), "Should be 2 chunks")
   325  				fileinfo, err = os.Stat(GetLocalLOBChunkPath(lobinfo.SHA, 0))
   326  				Expect(err).To(BeNil(), "Shouldn't be error opening stored LOB")
   327  				Expect(fileinfo.Size()).To(Equal(ChunkSize), "Stored LOB should be correct size")
   328  				fileinfo, err = os.Stat(GetLocalLOBChunkPath(lobinfo.SHA, 1))
   329  				Expect(err).To(BeNil(), "Shouldn't be error opening stored LOB")
   330  				Expect(fileinfo.Size()).To(Equal(ChunkSize), "Stored LOB should be correct size")
   331  
   332  			})
   333  
   334  		})
   335  
   336  		Context("Store large multiple chunk LOB [LONGTEST]", func() {
   337  
   338  			testFileName := path.Join(folders[2], "large.dat")
   339  			var correctLOBInfo *LOBInfo
   340  
   341  			BeforeEach(func() {
   342  				correctLOBInfo = CreateLargeTestLOBFileForStoring(testFileName)
   343  			})
   344  			AfterEach(func() {
   345  				os.Remove(testFileName)
   346  			})
   347  
   348  			It("correctly stores a large file", func() {
   349  				f, err := os.Open(testFileName)
   350  				if err != nil {
   351  					Fail(fmt.Sprintf("Can't reopen test file %v: %v", testFileName, err))
   352  				}
   353  				defer f.Close()
   354  				// Need to read leader for consistency with real usage
   355  				leader := make([]byte, SHALineLen)
   356  				c, err := f.Read(leader)
   357  				if err != nil {
   358  					Fail(fmt.Sprintf("Can't read leader of test file %v: %v", testFileName, err))
   359  				}
   360  				lobinfo, err := StoreLOB(f, leader[:c])
   361  				Expect(err).To(BeNil(), "Shouldn't be error storing LOB")
   362  				Expect(lobinfo).To(Equal(correctLOBInfo))
   363  				for i := 0; i < lobinfo.NumChunks; i++ {
   364  					fileinfo, err := os.Stat(GetLocalLOBChunkPath(lobinfo.SHA, i))
   365  					Expect(err).To(BeNil(), "Shouldn't be error opening stored LOB #%v", i)
   366  					if i+1 < lobinfo.NumChunks {
   367  						Expect(fileinfo.Size()).To(BeEquivalentTo(ChunkSize), "Stored LOB #%v should be chunk limit size", i)
   368  					} else {
   369  						Expect(fileinfo.Size()).To(BeEquivalentTo(lobinfo.Size%ChunkSize), "Stored LOB #%v should be correct size", i)
   370  					}
   371  
   372  				}
   373  			})
   374  
   375  		})
   376  
   377  	})
   378  
   379  	Describe("Retrieving a LOB", func() {
   380  		// Common git repo
   381  		var oldwd string
   382  		BeforeEach(func() {
   383  			oldwd, _ = os.Getwd()
   384  			// Set up git repo with some subfolders
   385  			CreateGitRepoForTest(root)
   386  			os.Chdir(root)
   387  
   388  			for _, f := range folders {
   389  				err := os.MkdirAll(f, 0755)
   390  				if err != nil {
   391  					fmt.Printf("Can't MkdirAll %v: %v", f, err)
   392  				}
   393  			}
   394  
   395  		})
   396  
   397  		AfterEach(func() {
   398  			os.Chdir(oldwd)
   399  			// Delete repo
   400  			err := ForceRemoveAll(root)
   401  			if err != nil {
   402  				Fail(err.Error())
   403  			}
   404  		})
   405  
   406  		Context("Retrieve small single chunk LOB", func() {
   407  			var correctLOBInfo *LOBInfo
   408  
   409  			BeforeEach(func() {
   410  				correctLOBInfo = CreateSmallTestLOBDataForRetrieval()
   411  			})
   412  
   413  			It("correctly retrieves small LOB file", func() {
   414  				// output to a temp file
   415  				out, err := ioutil.TempFile("", "lobsmall.dat")
   416  				Expect(err).To(BeNil(), "Shouldn't be error creating temp file")
   417  				outFilename := out.Name()
   418  				info, err := RetrieveLOB(correctLOBInfo.SHA, out)
   419  
   420  				Expect(err).To(BeNil(), "Shouldn't be error retrieving LOB")
   421  				out.Close()
   422  
   423  				Expect(info).To(Equal(correctLOBInfo), "Metadata should agree")
   424  				// Check output file
   425  				stat, err := os.Stat(outFilename)
   426  				Expect(err).To(BeNil(), "Shouldn't be error checking output file")
   427  				Expect(stat.Size()).To(Equal(info.Size), "Size on disk should agree with metadata")
   428  
   429  				os.Remove(outFilename)
   430  
   431  			})
   432  
   433  		})
   434  		Context("Retrieve large multiple chunk LOB [LONGTEST]", func() {
   435  			var correctLOBInfo *LOBInfo
   436  
   437  			BeforeEach(func() {
   438  				correctLOBInfo = CreateLargeTestLOBDataForRetrieval()
   439  			})
   440  
   441  			It("correctly retrieves large LOB file", func() {
   442  				// output to a temp file
   443  				out, err := ioutil.TempFile("", "loblarge.dat")
   444  				Expect(err).To(BeNil(), "Shouldn't be error creating temp file")
   445  				outFilename := out.Name()
   446  				info, err := RetrieveLOB(correctLOBInfo.SHA, out)
   447  
   448  				Expect(err).To(BeNil(), "Shouldn't be error retrieving LOB")
   449  				out.Close()
   450  
   451  				Expect(info).To(Equal(correctLOBInfo), "Metadata should agree")
   452  				// Check output file
   453  				stat, err := os.Stat(outFilename)
   454  				Expect(err).To(BeNil(), "Shouldn't be error checking output file")
   455  				Expect(stat.Size()).To(Equal(info.Size), "Size on disk should agree with metadata")
   456  
   457  				os.Remove(outFilename)
   458  
   459  			})
   460  
   461  		})
   462  
   463  		Context("Retrieve a zero size file", func() {
   464  			It("correctly retrieves zero size LOB file", func() {
   465  				// Create the zero size storage (separate test for storing)
   466  				infile := path.Join(folders[1], "zeroin.dat")
   467  				CreateRandomFileForTest(0, infile)
   468  				_, err := StoreLOBForTest(infile)
   469  				os.Remove(infile)
   470  				if err != nil {
   471  					Fail(fmt.Sprintf("Error storing zero size file %v", infile))
   472  				}
   473  
   474  				// Zero size file SHA
   475  				shaZero := sha1.New()
   476  				shaZeroStr := fmt.Sprintf("%x", string(shaZero.Sum(nil)))
   477  
   478  				// output to a temp file
   479  				out, err := ioutil.TempFile("", "lobzerotest.dat")
   480  				Expect(err).To(BeNil(), "Shouldn't be error creating temp file")
   481  				outFilename := out.Name()
   482  				info, err := RetrieveLOB(shaZeroStr, out)
   483  
   484  				Expect(err).To(BeNil(), "Shouldn't be error retrieving LOB")
   485  				out.Close()
   486  
   487  				Expect(info.SHA).To(Equal(shaZeroStr), "SHA should agree")
   488  				Expect(info.Size).To(BeEquivalentTo(0), "Should be zero size")
   489  				Expect(info.NumChunks).To(BeEquivalentTo(0), "Should be no chunks should agree")
   490  				// Check output file
   491  				stat, err := os.Stat(outFilename)
   492  				Expect(err).To(BeNil(), "Shouldn't be error checking output file")
   493  				Expect(stat.Size()).To(BeEquivalentTo(0), "Size on disk should be zero")
   494  
   495  				os.Remove(outFilename)
   496  
   497  			})
   498  
   499  		})
   500  
   501  	})
   502  
   503  	// --- Shared tests
   504  	Describe("Storing a LOB (shared store)", func() {
   505  		// Common git repo
   506  		var oldwd string
   507  		BeforeEach(func() {
   508  			oldwd, _ = os.Getwd()
   509  			os.MkdirAll(sharedStore, 0755)
   510  			GlobalOptions.SharedStore = sharedStore
   511  			// Set up git repo with some subfolders
   512  			CreateGitRepoForTest(root)
   513  
   514  			for _, f := range folders {
   515  				err := os.MkdirAll(f, 0755)
   516  				if err != nil {
   517  					fmt.Printf("Can't MkdirAll %v: %v", f, err)
   518  				}
   519  			}
   520  			os.Chdir(root)
   521  		})
   522  
   523  		AfterEach(func() {
   524  			os.Chdir(oldwd)
   525  			// Delete repo
   526  			err := ForceRemoveAll(root)
   527  			if err != nil {
   528  				Fail(err.Error())
   529  			}
   530  			err = ForceRemoveAll(sharedStore)
   531  			if err != nil {
   532  				Fail(err.Error())
   533  			}
   534  			GlobalOptions.SharedStore = ""
   535  		})
   536  
   537  		Context("Store small single chunk LOB (shared store)", func() {
   538  			testFileName := path.Join(folders[2], "small.dat")
   539  			var correctLOBInfo *LOBInfo
   540  
   541  			BeforeEach(func() {
   542  				correctLOBInfo = CreateSmallTestLOBFileForStoring(testFileName)
   543  			})
   544  			AfterEach(func() {
   545  				os.Remove(testFileName)
   546  			})
   547  
   548  			It("correctly stores a small file (shared store)", func() {
   549  				f, err := os.Open(testFileName)
   550  				if err != nil {
   551  					Fail(fmt.Sprintf("Can't reopen test file %v: %v", testFileName, err))
   552  				}
   553  				defer f.Close()
   554  				// Need to read leader for consistency with real usage
   555  				leader := make([]byte, SHALineLen)
   556  				c, err := f.Read(leader)
   557  				if err != nil {
   558  					Fail(fmt.Sprintf("Can't read leader of test file %v: %v", testFileName, err))
   559  				}
   560  				lobinfo, err := StoreLOB(f, leader[:c])
   561  				Expect(err).To(BeNil(), "Shouldn't be error storing LOB")
   562  				Expect(lobinfo).To(Equal(correctLOBInfo))
   563  
   564  				lobinfo, err = GetLOBInfo(correctLOBInfo.SHA)
   565  				Expect(err).To(BeNil(), "Shouldn't be error retrieving LOB info")
   566  				Expect(lobinfo).To(Equal(correctLOBInfo))
   567  
   568  				fileinfo, err := os.Stat(GetLocalLOBChunkPath(lobinfo.SHA, 0))
   569  				Expect(err).To(BeNil(), "Shouldn't be error opening stored LOB (local)")
   570  				Expect(fileinfo.Size()).To(Equal(lobinfo.Size), "Stored LOB should be correct size (local)")
   571  				// Also test shared
   572  				fileinfo, err = os.Stat(GetSharedLOBChunkPath(lobinfo.SHA, 0))
   573  				Expect(err).To(BeNil(), "Shouldn't be error opening stored LOB (shared)")
   574  				Expect(fileinfo.Size()).To(Equal(lobinfo.Size), "Stored LOB should be correct size (shared)")
   575  
   576  				links, err := GetHardLinkCount(GetLocalLOBChunkPath(lobinfo.SHA, 0))
   577  				Expect(err).To(BeNil(), "Shouldn't be error getting local LOB hard link info")
   578  				Expect(links).To(Equal(2), "Should be the right number of hard links (shared)")
   579  				links, err = GetHardLinkCount(GetSharedLOBChunkPath(lobinfo.SHA, 0))
   580  				Expect(err).To(BeNil(), "Shouldn't be error getting shared LOB hard link info")
   581  				Expect(links).To(Equal(2), "Should be the right number of hard links (local)")
   582  			})
   583  
   584  		})
   585  
   586  		Context("Store large multiple chunk LOB (shared store) [LONGTEST]", func() {
   587  
   588  			testFileName := path.Join(folders[2], "large.dat")
   589  			var correctLOBInfo *LOBInfo
   590  
   591  			BeforeEach(func() {
   592  				correctLOBInfo = CreateLargeTestLOBFileForStoring(testFileName)
   593  			})
   594  			AfterEach(func() {
   595  				os.Remove(testFileName)
   596  			})
   597  
   598  			It("correctly stores a large file (shared store)", func() {
   599  				f, err := os.Open(testFileName)
   600  				if err != nil {
   601  					Fail(fmt.Sprintf("Can't reopen test file %v: %v", testFileName, err))
   602  				}
   603  				defer f.Close()
   604  				// Need to read leader for consistency with real usage
   605  				leader := make([]byte, SHALineLen)
   606  				c, err := f.Read(leader)
   607  				if err != nil {
   608  					Fail(fmt.Sprintf("Can't read leader of test file %v: %v", testFileName, err))
   609  				}
   610  				lobinfo, err := StoreLOB(f, leader[:c])
   611  				Expect(err).To(BeNil(), "Shouldn't be error storing LOB")
   612  				Expect(lobinfo).To(Equal(correctLOBInfo))
   613  				lobinfo, err = GetLOBInfo(correctLOBInfo.SHA)
   614  				Expect(err).To(BeNil(), "Shouldn't be error retrieving LOB info")
   615  				Expect(lobinfo).To(Equal(correctLOBInfo))
   616  
   617  				for i := 0; i < lobinfo.NumChunks; i++ {
   618  					fileinfo, err := os.Stat(GetLocalLOBChunkPath(lobinfo.SHA, i))
   619  					Expect(err).To(BeNil(), "Shouldn't be error opening stored LOB #%v", i)
   620  					if i+1 < lobinfo.NumChunks {
   621  						Expect(fileinfo.Size()).To(BeEquivalentTo(ChunkSize), "Stored LOB #%v should be chunk limit size", i)
   622  					} else {
   623  						Expect(fileinfo.Size()).To(BeEquivalentTo(lobinfo.Size%ChunkSize), "Stored LOB #%v should be correct size", i)
   624  					}
   625  					// Also check shared
   626  					fileinfo, err = os.Stat(GetSharedLOBChunkPath(lobinfo.SHA, i))
   627  					Expect(err).To(BeNil(), "Shouldn't be error opening stored LOB #%v", i)
   628  					if i+1 < lobinfo.NumChunks {
   629  						Expect(fileinfo.Size()).To(BeEquivalentTo(ChunkSize), "Stored LOB #%v should be chunk limit size", i)
   630  					} else {
   631  						Expect(fileinfo.Size()).To(BeEquivalentTo(lobinfo.Size%ChunkSize), "Stored LOB #%v should be correct size", i)
   632  					}
   633  					links, err := GetHardLinkCount(GetLocalLOBChunkPath(lobinfo.SHA, i))
   634  					Expect(err).To(BeNil(), "Shouldn't be error getting local LOB hard link info")
   635  					Expect(links).To(Equal(2), "Should be the right number of hard links (shared)")
   636  					links, err = GetHardLinkCount(GetSharedLOBChunkPath(lobinfo.SHA, i))
   637  					Expect(err).To(BeNil(), "Shouldn't be error getting shared LOB hard link info")
   638  					Expect(links).To(Equal(2), "Should be the right number of hard links (local)")
   639  
   640  				}
   641  			})
   642  
   643  		})
   644  
   645  	})
   646  
   647  	Describe("Retrieving a LOB (shared store)", func() {
   648  		// Common git repo
   649  		var oldwd string
   650  		BeforeEach(func() {
   651  			os.MkdirAll(sharedStore, 0755)
   652  			oldwd, _ = os.Getwd()
   653  			GlobalOptions.SharedStore = sharedStore
   654  			// Set up git repo with some subfolders
   655  			CreateGitRepoForTest(root)
   656  
   657  			for _, f := range folders {
   658  				err := os.MkdirAll(f, 0755)
   659  				if err != nil {
   660  					fmt.Printf("Can't MkdirAll %v: %v", f, err)
   661  				}
   662  			}
   663  			os.Chdir(root)
   664  
   665  		})
   666  
   667  		AfterEach(func() {
   668  			os.Chdir(oldwd)
   669  			// Delete repo
   670  			err := ForceRemoveAll(root)
   671  			if err != nil {
   672  				Fail(err.Error())
   673  			}
   674  			err = ForceRemoveAll(sharedStore)
   675  			if err != nil {
   676  				Fail(err.Error())
   677  			}
   678  			GlobalOptions.SharedStore = ""
   679  		})
   680  
   681  		Context("Retrieve small single chunk LOB (shared store)", func() {
   682  			var correctLOBInfo *LOBInfo
   683  
   684  			BeforeEach(func() {
   685  				correctLOBInfo = CreateSmallTestLOBDataForRetrieval()
   686  			})
   687  
   688  			It("correctly retrieves small LOB file (shared store)", func() {
   689  				// output to a temp file
   690  				out, err := ioutil.TempFile("", "lobsmall.dat")
   691  				Expect(err).To(BeNil(), "Shouldn't be error creating temp file")
   692  				outFilename := out.Name()
   693  				info, err := RetrieveLOB(correctLOBInfo.SHA, out)
   694  
   695  				Expect(err).To(BeNil(), "Shouldn't be error retrieving LOB")
   696  				out.Close()
   697  
   698  				Expect(info).To(Equal(correctLOBInfo), "Metadata should agree")
   699  				// Check output file
   700  				stat, err := os.Stat(outFilename)
   701  				Expect(err).To(BeNil(), "Shouldn't be error checking output file")
   702  				Expect(stat.Size()).To(Equal(info.Size), "Size on disk should agree with metadata")
   703  
   704  				os.Remove(outFilename)
   705  
   706  			})
   707  
   708  		})
   709  		Context("Retrieve large multiple chunk LOB (shared store) [LONGTEST]", func() {
   710  			var correctLOBInfo *LOBInfo
   711  
   712  			BeforeEach(func() {
   713  				correctLOBInfo = CreateLargeTestLOBDataForRetrieval()
   714  			})
   715  
   716  			It("correctly retrieves large LOB file (shared store)", func() {
   717  				// output to a temp file
   718  				out, err := ioutil.TempFile("", "loblarge.dat")
   719  				Expect(err).To(BeNil(), "Shouldn't be error creating temp file")
   720  				outFilename := out.Name()
   721  				info, err := RetrieveLOB(correctLOBInfo.SHA, out)
   722  
   723  				Expect(err).To(BeNil(), "Shouldn't be error retrieving LOB")
   724  				out.Close()
   725  
   726  				Expect(info).To(Equal(correctLOBInfo), "Metadata should agree")
   727  				// Check output file
   728  				stat, err := os.Stat(outFilename)
   729  				Expect(err).To(BeNil(), "Shouldn't be error checking output file")
   730  				Expect(stat.Size()).To(Equal(info.Size), "Size on disk should agree with metadata")
   731  
   732  				os.Remove(outFilename)
   733  
   734  			})
   735  
   736  		})
   737  
   738  	})
   739  	Describe("Getting & checking LOB files", func() {
   740  		var lobinfos []*LOBInfo
   741  		var origDir string
   742  		var smallFileIdx []int
   743  		var midFileIdx []int
   744  		var largeFileIdx []int
   745  		var savedChunkSize int64
   746  		BeforeEach(func() {
   747  			CreateGitRepoForTest(root)
   748  			origDir, _ = os.Getwd()
   749  			os.Chdir(root)
   750  
   751  			files := []string{
   752  				"smallfile1.bin",
   753  				"smallfile2.bin",
   754  				"smallfile3.bin",
   755  				"midfile1.bin",
   756  				"midfile2.bin",
   757  				"midfile3.bin",
   758  				"largefile1.bin",
   759  				"largefile2.bin"}
   760  
   761  			// Reduce global chunk size for test
   762  			// we need to test many chunks but let's not take lots of time
   763  			savedChunkSize = ChunkSize
   764  			ChunkSize = 16384
   765  
   766  			sizes := []int64{50, 150, 200,
   767  				ChunkSize + 100,
   768  				ChunkSize + 1200,
   769  				ChunkSize + 3400,
   770  				ChunkSize*3 - 200,
   771  				ChunkSize*3 - 1000}
   772  
   773  			smallFileIdx = []int{0, 1, 2}
   774  			midFileIdx = []int{3, 4, 5}
   775  			largeFileIdx = []int{6, 7}
   776  
   777  			// Create a bunch of files
   778  			lobinfos = make([]*LOBInfo, 0, len(files))
   779  			for i, f := range files {
   780  				sz := sizes[i]
   781  				filename := path.Join(root, f)
   782  				CreateRandomFileForTest(sz, filename)
   783  				info, err := StoreLOBForTest(filename)
   784  				if err != nil {
   785  					Fail(err.Error())
   786  				}
   787  				lobinfos = append(lobinfos, info)
   788  			}
   789  
   790  		})
   791  		AfterEach(func() {
   792  			os.Chdir(origDir)
   793  			// Delete repo
   794  			err := ForceRemoveAll(root)
   795  			if err != nil {
   796  				Fail(err.Error())
   797  			}
   798  
   799  			ChunkSize = savedChunkSize
   800  		})
   801  
   802  		It("Shallow checks LOB files", func() {
   803  			// Initial test, everything should validate (just use check)
   804  			basedir := GetLocalLOBRoot()
   805  			for _, li := range lobinfos {
   806  				files, sz, err := GetLOBFilesForSHA(li.SHA, basedir, true, false)
   807  				Expect(err).To(BeNil(), "Should be no error when checking LOB file for %v", li.SHA)
   808  				Expect(files).To(HaveLen(li.NumChunks+1), "Should have the right number of files")
   809  				Expect(sz).To(BeEquivalentTo(li.Size), "Total size should be correct")
   810  			}
   811  
   812  			// Test for simple corruptions
   813  			// Remove a meta file
   814  			var err error
   815  			metafile := GetLocalLOBMetaPath(lobinfos[smallFileIdx[0]].SHA)
   816  			os.Remove(metafile)
   817  			err = CheckLOBFilesForSHA(lobinfos[smallFileIdx[0]].SHA, basedir, false)
   818  			Expect(err).ToNot(BeNil(), "Should detect missing meta file")
   819  
   820  			var chunkfile string
   821  			// Remove a chunk file (only one)
   822  			chunkfile = GetLocalLOBChunkPath(lobinfos[smallFileIdx[1]].SHA, 0)
   823  			os.Remove(chunkfile)
   824  			err = CheckLOBFilesForSHA(lobinfos[smallFileIdx[1]].SHA, basedir, false)
   825  			Expect(err).ToNot(BeNil(), "Should detect missing chunk file for single-chunk file")
   826  			// Remove a chunk file (one of many - first)
   827  			chunkfile = GetLocalLOBChunkPath(lobinfos[midFileIdx[0]].SHA, 0)
   828  			os.Remove(chunkfile)
   829  			err = CheckLOBFilesForSHA(lobinfos[midFileIdx[0]].SHA, basedir, false)
   830  			Expect(err).ToNot(BeNil(), "Should detect missing first chunk file for 2-chunk file")
   831  			// Remove a chunk file (one of many - last)
   832  			chunkfile = GetLocalLOBChunkPath(lobinfos[midFileIdx[1]].SHA, 1)
   833  			os.Remove(chunkfile)
   834  			err = CheckLOBFilesForSHA(lobinfos[midFileIdx[1]].SHA, basedir, false)
   835  			Expect(err).ToNot(BeNil(), "Should detect missing second chunk file for 2-chunk file")
   836  
   837  			// Change the size of a chunk file (single chunk)
   838  			chunkfile = GetLocalLOBChunkPath(lobinfos[smallFileIdx[2]].SHA, 0)
   839  			f, _ := os.OpenFile(chunkfile, os.O_APPEND|os.O_RDWR, 0644)
   840  			f.Write([]byte("icorruptthee"))
   841  			f.Close()
   842  			err = CheckLOBFilesForSHA(lobinfos[smallFileIdx[2]].SHA, basedir, false)
   843  			Expect(err).ToNot(BeNil(), "Should detect incorrect size chunk file for single-chunk file")
   844  			// Change the size of a chunk file (one of many - first)
   845  			chunkfile = GetLocalLOBChunkPath(lobinfos[midFileIdx[2]].SHA, 0)
   846  			f, _ = os.OpenFile(chunkfile, os.O_APPEND|os.O_RDWR, 0644)
   847  			f.Write([]byte("hssss"))
   848  			f.Close()
   849  			err = CheckLOBFilesForSHA(lobinfos[midFileIdx[2]].SHA, basedir, false)
   850  			Expect(err).ToNot(BeNil(), "Should detect incorrect size chunk file for multi-chunk file (first)")
   851  			// Change the size of a chunk file (one of many - middle)
   852  			chunkfile = GetLocalLOBChunkPath(lobinfos[largeFileIdx[0]].SHA, 1)
   853  			f, _ = os.OpenFile(chunkfile, os.O_APPEND|os.O_RDWR, 0644)
   854  			f.Write([]byte("itburns"))
   855  			f.Close()
   856  			err = CheckLOBFilesForSHA(lobinfos[largeFileIdx[0]].SHA, basedir, false)
   857  			Expect(err).ToNot(BeNil(), "Should detect incorrect size chunk file for multi-chunk file (middle)")
   858  			// Change the size of a chunk file (one of many - last)
   859  			chunkfile = GetLocalLOBChunkPath(lobinfos[largeFileIdx[1]].SHA, lobinfos[largeFileIdx[1]].NumChunks-1)
   860  			f, _ = os.OpenFile(chunkfile, os.O_APPEND|os.O_RDWR, 0644)
   861  			f.Write([]byte("ngggg"))
   862  			f.Close()
   863  			err = CheckLOBFilesForSHA(lobinfos[largeFileIdx[1]].SHA, basedir, false)
   864  			Expect(err).ToNot(BeNil(), "Should detect incorrect size chunk file for multi-chunk file (last)")
   865  
   866  		})
   867  
   868  		It("Deep checks LOB files", func() {
   869  			// Initial test, everything should validate (just use check)
   870  			basedir := GetLocalLOBRoot()
   871  			for _, li := range lobinfos {
   872  				files, sz, err := GetLOBFilesForSHA(li.SHA, basedir, true, true)
   873  				Expect(err).To(BeNil(), "Should be no error when checking LOB file for %v", li.SHA)
   874  				Expect(files).To(HaveLen(li.NumChunks+1), "Should have the right number of files")
   875  				Expect(sz).To(BeEquivalentTo(li.Size), "Total size should be correct")
   876  			}
   877  
   878  			// Test for deep corruptions
   879  			var chunkfile string
   880  			var err error
   881  			// Change 2 bytes of a chunk file, size unchanged (single chunk)
   882  			chunkfile = GetLocalLOBChunkPath(lobinfos[smallFileIdx[0]].SHA, 0)
   883  			f, _ := os.OpenFile(chunkfile, os.O_RDWR|os.O_SYNC, 0644)
   884  			f.Seek(10, os.SEEK_SET)
   885  			f.Write([]byte("qq"))
   886  			f.Close()
   887  			// check that we wouldn't detect this without checking the SHA
   888  			err = CheckLOBFilesForSHA(lobinfos[smallFileIdx[0]].SHA, basedir, false)
   889  			Expect(err).To(BeNil(), "Should not detect the corruption without deep hash check")
   890  			err = CheckLOBFilesForSHA(lobinfos[smallFileIdx[0]].SHA, basedir, true)
   891  			Expect(err).ToNot(BeNil(), "Should detect the corruption with deep hash check")
   892  			// Change 2 bytes of a chunk file, size unchanged (multiple chunk)
   893  			chunkfile = GetLocalLOBChunkPath(lobinfos[midFileIdx[0]].SHA, 1)
   894  			f, _ = os.OpenFile(chunkfile, os.O_RDWR|os.O_SYNC, 0644)
   895  			f.Seek(51, os.SEEK_SET)
   896  			f.Write([]byte("zf"))
   897  			f.Close()
   898  			err = CheckLOBFilesForSHA(lobinfos[midFileIdx[0]].SHA, basedir, false)
   899  			Expect(err).To(BeNil(), "Should not detect the corruption without deep hash check (second chunk)")
   900  			err = CheckLOBFilesForSHA(lobinfos[midFileIdx[0]].SHA, basedir, true)
   901  			Expect(err).ToNot(BeNil(), "Should detect the corruption with deep hash check (second chunk)")
   902  
   903  		})
   904  
   905  	})
   906  
   907  })