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(&copyLoopNode, &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  })