github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/cmn/tests/common_test.go (about) 1 // Package test provides tests for common low-level types and utilities for all aistore projects 2 /* 3 * Copyright (c) 2018-2023, NVIDIA CORPORATION. All rights reserved. 4 */ 5 package tests_test 6 7 import ( 8 "crypto/rand" 9 "io" 10 "os" 11 "path/filepath" 12 "reflect" 13 14 "github.com/NVIDIA/aistore/cmn/cos" 15 . "github.com/onsi/ginkgo/v2" 16 . "github.com/onsi/gomega" 17 ) 18 19 var _ = Describe("Common file", func() { 20 const ( 21 tmpDir = "/tmp/cmn-tests" 22 ) 23 24 var ( 25 nonExistingFile = filepath.Join(tmpDir, "file.txt") 26 nonExistingRenamedFile = filepath.Join(tmpDir, "some", "path", "fi.txt") 27 nonExistingPath = filepath.Join(tmpDir, "non", "existing", "directory") 28 ) 29 30 createFile := func(fqn string) { 31 file, err := cos.CreateFile(fqn) 32 Expect(err).NotTo(HaveOccurred()) 33 Expect(file.Close()).NotTo(HaveOccurred()) 34 Expect(fqn).To(BeARegularFile()) 35 } 36 37 validateSaveReaderOutput := func(fqn string, sourceData []byte) { 38 Expect(fqn).To(BeARegularFile()) 39 40 data, err := os.ReadFile(fqn) 41 Expect(err).NotTo(HaveOccurred()) 42 Expect(reflect.DeepEqual(data, sourceData)).To(BeTrue()) 43 } 44 45 AfterEach(func() { 46 os.RemoveAll(tmpDir) 47 }) 48 49 Context("CopyStruct", func() { 50 type ( 51 Tree struct { 52 left, right *Tree 53 value int 54 } 55 56 NonPrimitiveStruct struct { 57 m map[string]int 58 s []int 59 } 60 ) 61 62 It("should correctly copy empty struct", func() { 63 var emptySructResult struct{} 64 cos.CopyStruct(&emptySructResult, &struct{}{}) 65 66 Expect(reflect.DeepEqual(struct{}{}, emptySructResult)).To(BeTrue()) 67 }) 68 69 It("should correctly copy self-referencing struct", func() { 70 loopNode := Tree{} 71 loopNode.left, loopNode.right, loopNode.value = &loopNode, &loopNode, 0 72 var copyLoopNode Tree 73 cos.CopyStruct(©LoopNode, &loopNode) 74 75 Expect(loopNode).To(Equal(copyLoopNode)) 76 77 loopNode.value += 100 78 79 Expect(loopNode).NotTo(Equal(copyLoopNode)) 80 }) 81 82 It("should correctly copy nested structs, perisiting references", func() { 83 left := Tree{nil, nil, 0} 84 right := Tree{nil, nil, 1} 85 root := Tree{&left, &right, 2} 86 var rootCopy Tree 87 cos.CopyStruct(&rootCopy, &root) 88 89 Expect(root).To(Equal(rootCopy)) 90 91 left.value += 100 92 93 Expect(root).To(Equal(rootCopy)) 94 }) 95 96 It("should correctly copy structs with nonprimitive types", func() { 97 nonPrimitive := NonPrimitiveStruct{} 98 nonPrimitive.m = make(map[string]int) 99 nonPrimitive.m["one"] = 1 100 nonPrimitive.m["two"] = 2 101 nonPrimitive.s = []int{1, 2} 102 var nonPrimitiveCopy NonPrimitiveStruct 103 cos.CopyStruct(&nonPrimitiveCopy, &nonPrimitive) 104 105 Expect(nonPrimitive).To(Equal(nonPrimitive)) 106 107 nonPrimitive.m["one"] = 0 108 nonPrimitive.s[0] = 0 109 110 Expect(nonPrimitive).To(Equal(nonPrimitive)) 111 }) 112 }) 113 114 Context("SaveReader", func() { 115 It("should save the reader content into a file", func() { 116 const bytesToRead = 1000 117 byteBuffer := make([]byte, bytesToRead) 118 119 _, err := cos.SaveReader(nonExistingFile, rand.Reader, byteBuffer, cos.ChecksumNone, bytesToRead) 120 Expect(err).NotTo(HaveOccurred()) 121 122 validateSaveReaderOutput(nonExistingFile, byteBuffer) 123 }) 124 125 It("should save the reader without specified size", func() { 126 const bytesLimit = 500 127 byteBuffer := make([]byte, bytesLimit*2) 128 reader := &io.LimitedReader{R: rand.Reader, N: bytesLimit} 129 130 _, err := cos.SaveReader(nonExistingFile, reader, byteBuffer, cos.ChecksumNone, -1) 131 Expect(err).NotTo(HaveOccurred()) 132 133 validateSaveReaderOutput(nonExistingFile, byteBuffer[:bytesLimit]) 134 }) 135 }) 136 137 Context("CreateFile", func() { 138 It("should create a file when it does not exist", func() { 139 createFile(nonExistingFile) 140 }) 141 142 It("should not complain when creating a file which already exists", func() { 143 createFile(nonExistingFile) 144 createFile(nonExistingFile) 145 }) 146 }) 147 148 Context("CreateDir", func() { 149 It("should successfully create directory", func() { 150 err := cos.CreateDir(nonExistingPath) 151 Expect(err).NotTo(HaveOccurred()) 152 153 Expect(nonExistingPath).To(BeADirectory()) 154 }) 155 156 It("should not error when creating directory which already exists", func() { 157 err := cos.CreateDir(nonExistingPath) 158 Expect(err).NotTo(HaveOccurred()) 159 err = cos.CreateDir(nonExistingPath) 160 Expect(err).NotTo(HaveOccurred()) 161 162 Expect(nonExistingPath).To(BeADirectory()) 163 }) 164 165 It("should error when directory is not valid", func() { 166 err := cos.CreateDir("") 167 Expect(err).To(HaveOccurred()) 168 }) 169 }) 170 171 Context("CopyFile", func() { 172 var ( 173 srcFilename = filepath.Join(tmpDir, "copyfilesrc.txt") 174 dstFilename = filepath.Join(tmpDir, "copyfiledst.txt") 175 ) 176 177 It("should copy file and preserve the content", func() { 178 _, err := cos.SaveReader(srcFilename, rand.Reader, make([]byte, 1000), cos.ChecksumNone, 1000) 179 Expect(err).NotTo(HaveOccurred()) 180 _, _, err = cos.CopyFile(srcFilename, dstFilename, make([]byte, 1000), cos.ChecksumNone) 181 Expect(err).NotTo(HaveOccurred()) 182 183 srcData, err := os.ReadFile(srcFilename) 184 Expect(err).NotTo(HaveOccurred()) 185 186 dstData, err := os.ReadFile(dstFilename) 187 Expect(err).NotTo(HaveOccurred()) 188 189 Expect(srcData).To(Equal(dstData)) 190 }) 191 192 It("should copy a object and compute its checksum", func() { 193 expectedCksum, err := cos.SaveReader(srcFilename, rand.Reader, make([]byte, 1000), cos.ChecksumXXHash, 1000) 194 Expect(err).NotTo(HaveOccurred()) 195 196 _, cksum, err := cos.CopyFile(srcFilename, dstFilename, make([]byte, 1000), cos.ChecksumXXHash) 197 Expect(err).NotTo(HaveOccurred()) 198 Expect(cksum).To(Equal(expectedCksum)) 199 200 srcData, err := os.ReadFile(srcFilename) 201 Expect(err).NotTo(HaveOccurred()) 202 203 dstData, err := os.ReadFile(dstFilename) 204 Expect(err).NotTo(HaveOccurred()) 205 206 Expect(srcData).To(Equal(dstData)) 207 }) 208 }) 209 210 Context("Rename", func() { 211 It("should not error when dst file does not exist", func() { 212 createFile(nonExistingFile) 213 214 err := cos.Rename(nonExistingFile, nonExistingRenamedFile) 215 Expect(err).NotTo(HaveOccurred()) 216 217 Expect(nonExistingRenamedFile).To(BeARegularFile()) 218 Expect(nonExistingFile).NotTo(BeAnExistingFile()) 219 }) 220 221 It("should not error when dst file already exists", func() { 222 createFile(nonExistingFile) 223 createFile(nonExistingRenamedFile) 224 225 err := cos.Rename(nonExistingFile, nonExistingRenamedFile) 226 Expect(err).NotTo(HaveOccurred()) 227 228 Expect(nonExistingRenamedFile).To(BeARegularFile()) 229 Expect(nonExistingFile).NotTo(BeAnExistingFile()) 230 }) 231 232 It("should error when src does not exist", func() { 233 err := cos.Rename("/some/non/existing/file.txt", "/tmp/file.txt") 234 Expect(err).To(HaveOccurred()) 235 }) 236 }) 237 238 Context("RemoveFile", func() { 239 AfterEach(func() { 240 os.RemoveAll(tmpDir) 241 }) 242 243 It("should remove regular file", func() { 244 createFile(nonExistingFile) 245 246 err := cos.RemoveFile(nonExistingFile) 247 Expect(err).NotTo(HaveOccurred()) 248 Expect(nonExistingFile).NotTo(BeAnExistingFile()) 249 }) 250 251 It("should not complain when regular file does not exist", func() { 252 err := cos.RemoveFile("/some/non/existing/file.txt") 253 Expect(err).NotTo(HaveOccurred()) 254 }) 255 }) 256 257 Context("ParseQuantity", func() { 258 DescribeTable("parse quantity without error", 259 func(quantity, ty string, value int) { 260 pq, err := cos.ParseQuantity(quantity) 261 Expect(err).NotTo(HaveOccurred()) 262 263 Expect(pq).To(Equal(cos.ParsedQuantity{Type: ty, Value: uint64(value)})) 264 }, 265 Entry("simple number", "80B", cos.QuantityBytes, 80), 266 Entry("simple percent", "80%", cos.QuantityPercent, 80), 267 Entry("number with spaces", " 8 0 KB ", cos.QuantityBytes, 80*cos.KiB), 268 Entry("percent with spaces", "80 %", cos.QuantityPercent, 80), 269 ) 270 271 DescribeTable("parse quantity with error", 272 func(template string) { 273 _, err := cos.ParseQuantity(template) 274 Expect(err).Should(HaveOccurred()) 275 }, 276 Entry("contains alphabet", "a80B"), 277 Entry("multiple percent signs", "80%%"), 278 Entry("empty percent sign", "%"), 279 Entry("negative number", "-1000"), 280 Entry("101 percent", "101%"), 281 Entry("-1 percent", "-1%"), 282 ) 283 }) 284 285 Context("ParseBool", func() { 286 It("should correctly parse different values into bools", func() { 287 trues := []string{"y", "yes", "on", "1", "t", "T", "true", "TRUE", "True"} 288 falses := []string{"n", "no", "off", "0", "f", "F", "false", "FALSE", "False", ""} 289 errs := []string{"2", "enable", "nothing"} 290 291 for _, s := range trues { 292 v, err := cos.ParseBool(s) 293 Expect(err).NotTo(HaveOccurred()) 294 Expect(v).To(BeTrue()) 295 } 296 297 for _, s := range falses { 298 v, err := cos.ParseBool(s) 299 Expect(err).NotTo(HaveOccurred()) 300 Expect(v).To(BeFalse()) 301 } 302 303 for _, s := range errs { 304 _, err := cos.ParseBool(s) 305 Expect(err).To(HaveOccurred()) 306 } 307 }) 308 }) 309 310 Context("StrSlicesEqual", func() { 311 DescribeTable("parse quantity with error", 312 func(lhs, rhs []string, expected bool) { 313 Expect(cos.StrSlicesEqual(lhs, rhs)).To(Equal(expected)) 314 }, 315 Entry("empty slices", []string{}, []string{}, true), 316 Entry("single item", []string{"one"}, []string{"one"}, true), 317 Entry("multiple items", []string{"one", "two", "three"}, []string{"one", "two", "three"}, true), 318 Entry("multiple items in different order", []string{"two", "three", "one"}, []string{"one", "two", "three"}, true), 319 320 Entry("empty and single item", []string{"one"}, []string{}, false), 321 Entry("empty and single item (swapped)", []string{}, []string{"one"}, false), 322 Entry("same number of elements but different content", []string{"two", "three", "four"}, []string{"one", "two", "three"}, false), 323 Entry("same number of elements but different content (swapped)", []string{"two", "three", "one"}, []string{"four", "two", "three"}, false), 324 ) 325 }) 326 327 Context("ExpandPath", func() { 328 It("should expand short path with current home", func() { 329 shortPath := "~" 330 path := cos.ExpandPath(shortPath) 331 Expect(path).ToNot(Equal(shortPath)) 332 }) 333 334 It("should expand long path with current home", func() { 335 longPath := "~/tmp" 336 path := cos.ExpandPath(longPath) 337 Expect(path).ToNot(Equal(longPath)) 338 }) 339 340 It("should not expand path when prefixed with more than one tilde", func() { 341 shortPath := "~~.tmp" 342 path := cos.ExpandPath(shortPath) 343 Expect(path).To(Equal(shortPath)) 344 }) 345 346 It("should expand empty path to current directory (dot)", func() { 347 emptyPath := "" 348 path := cos.ExpandPath(emptyPath) 349 Expect(path).To(Equal(".")) 350 }) 351 }) 352 })