| // Copyright 2024 Google LLC | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package gcs | |
| import ( | |
| "bytes" | |
| "context" | |
| "fmt" | |
| "os" | |
| "path" | |
| "path/filepath" | |
| "sort" | |
| "testing" | |
| "time" | |
| "cos.googlesource.com/cos/tools.git/src/pkg/fakes" | |
| "github.com/google/go-cmp/cmp" | |
| ) | |
| // TestListGCSBucket is testing the functionality to lists all the objectNames in gcsBucket with prefix. | |
| func TestListGCSBucket(t *testing.T) { | |
| fakeGCS := fakes.GCSForTest(t) | |
| fakeBucket := "cos-tools" | |
| fakeGCSClient := fakeGCS.Client | |
| ctx := context.Background() | |
| var fakeData = []struct { | |
| artifactPath string | |
| artifactContent string | |
| }{ | |
| { | |
| "10000.00.00/lakitu/gpu_535_version", | |
| "535.1.1", | |
| }, | |
| { | |
| "10000.00.00/lakitu/gpu_525_version", | |
| "525.1.1", | |
| }, | |
| { | |
| "10000.00.00/lakitu/cpu_410_version", | |
| "empty", | |
| }, | |
| { | |
| "10000.00.00/lakitu/empty_file", | |
| "", | |
| }, | |
| } | |
| for _, data := range fakeData { | |
| fakeGCS.Objects[path.Join("/", fakeBucket, data.artifactPath)] = []byte(data.artifactContent) | |
| } | |
| defer fakeGCS.Close() | |
| var testCases = []struct { | |
| prefix string | |
| expected []string | |
| }{ | |
| { | |
| "10000", | |
| []string{"10000.00.00/lakitu/gpu_535_version", "10000.00.00/lakitu/gpu_525_version", "10000.00.00/lakitu/cpu_410_version", "10000.00.00/lakitu/empty_file"}, | |
| }, | |
| { | |
| "10000.00.00/lakitu/cpu_", | |
| []string{"10000.00.00/lakitu/cpu_410_version"}, | |
| }, | |
| { | |
| "10000.00.00/lakitu/empty_file", | |
| []string{"10000.00.00/lakitu/empty_file"}, | |
| }, | |
| { | |
| "999", | |
| nil, | |
| }, | |
| } | |
| for index, tc := range testCases { | |
| t.Run(fmt.Sprintf("Test %d: List artifacts with prefix:%s.", index, tc.prefix), func(t *testing.T) { | |
| actualResult, err := ListGCSBucket(ctx, fakeGCSClient, fakeBucket, tc.prefix) | |
| if err != nil { | |
| t.Fatalf("TestListArtifacts Failed: fail to list the artifact with prefix: %s from the fake GCS client in bucket: %s with error: %v", tc.prefix, fakeBucket, err) | |
| } | |
| sort.Strings(actualResult) | |
| sort.Strings(tc.expected) | |
| if !cmp.Equal(actualResult, tc.expected) { | |
| t.Errorf("TestListArtifacts Failed: listing the artifact with prefix: %s from the fake GCS client in bucket: %s, the expected result is: %s, but the actual result is: %s.", tc.prefix, fakeBucket, tc.expected, actualResult) | |
| } | |
| }) | |
| } | |
| } | |
| func TestUploadDownloadDelete(t *testing.T) { | |
| var err error | |
| fakeGCS := fakes.GCSForTest(t) | |
| ctx := context.Background() | |
| testData := []struct { | |
| path string | |
| data string | |
| }{ | |
| { | |
| "a/b.txt", | |
| "abc", | |
| }, | |
| { | |
| "a/c/d.txt", | |
| "def", | |
| }, | |
| { | |
| "a/c/e.txt", | |
| "ghi", | |
| }, | |
| } | |
| src := t.TempDir() | |
| for _, data := range testData { | |
| srcDir := path.Join(src, filepath.Dir(data.path)) | |
| err = os.MkdirAll(srcDir, 0777) | |
| if err != nil { | |
| t.Fatalf("could not make dir %s: %v", srcDir, err) | |
| } | |
| srcPath := path.Join(src, data.path) | |
| err = os.WriteFile(srcPath, []byte(data.data), 0666) | |
| if err != nil { | |
| t.Fatalf("could not write data to file %s: %v", srcPath, err) | |
| } | |
| } | |
| bucketPrefixes := []string{"tmp/test/prefix", "tmp/test/prefix/"} | |
| for _, prefix := range bucketPrefixes { | |
| dst := t.TempDir() | |
| bucket := NewGCSBucket(fakeGCS.Client, "test", prefix) | |
| defer func() { | |
| err := bucket.DeleteDir(ctx, src) | |
| if err != nil { | |
| t.Fatalf("failed to clean up %s; you will need to clean it up manually", bucket.URI(src)) | |
| } | |
| }() | |
| err = bucket.UploadDir(ctx, src, src) | |
| if err != nil { | |
| t.Fatalf("could not upload path (%v)", err) | |
| } | |
| err = bucket.DownloadDir(ctx, src, dst) | |
| if err != nil { | |
| t.Fatalf("could not download dir (%v)", err) | |
| } | |
| for _, data := range testData { | |
| dstPath := path.Join(dst, data.path) | |
| contents, err := os.ReadFile(dstPath) | |
| if err != nil { | |
| t.Fatalf("could not read contents for %s: %v", dstPath, err) | |
| } | |
| if !bytes.Equal(contents, []byte(data.data)) { | |
| t.Fatalf("wrong contents for %s (expected %s, got %s)", dstPath, contents, data.data) | |
| } | |
| } | |
| } | |
| } | |
| func TestListDir(t *testing.T) { | |
| var err error | |
| fakeGCS := fakes.GCSForTest(t) | |
| bucket := NewGCSBucket(fakeGCS.Client, "test", "tmp/test/prefix") | |
| ctx := context.Background() | |
| src := t.TempDir() | |
| testPaths := []string{ | |
| "a/b.txt", | |
| "a/c/d.txt", | |
| "a/c/e.txt", | |
| "f/g.txt", | |
| } | |
| for _, p := range testPaths { | |
| srcDir := path.Join(src, filepath.Dir(p)) | |
| err = os.MkdirAll(srcDir, 0777) | |
| if err != nil { | |
| t.Fatalf("could not make dir %s: %v", srcDir, err) | |
| } | |
| srcPath := path.Join(src, p) | |
| err = os.WriteFile(srcPath, []byte("abc"), 0666) | |
| if err != nil { | |
| t.Fatalf("could not write data to file %s: %v", srcPath, err) | |
| } | |
| } | |
| defer func() { | |
| err := bucket.DeleteDir(ctx, src) | |
| if err != nil { | |
| t.Fatalf("failed to clean up %s; you will need to clean it up manually", bucket.URI(src)) | |
| } | |
| }() | |
| err = bucket.UploadDir(ctx, src, src) | |
| if err != nil { | |
| t.Fatalf("could not upload path (%v)", err) | |
| } | |
| entries, err := bucket.ListDir(ctx, path.Join(src, "a")) | |
| if err != nil { | |
| t.Fatalf("could not list dir (%v)", err) | |
| } | |
| sort.Strings(entries) | |
| expected := []string{path.Join(src, "a/b.txt"), path.Join(src, "a/c") + "/"} | |
| sort.Strings(expected) | |
| fmt.Printf("entries: %v\n", entries) | |
| fmt.Printf("expected: %v\n", expected) | |
| if diff := cmp.Diff(expected, entries); diff != "" { | |
| t.Errorf("listed entries did not match expected;\ndiff: %s", diff) | |
| } | |
| } | |
| func TestDownloadParallel(t *testing.T) { | |
| var err error | |
| fakeGCS := fakes.GCSForTest(t) | |
| fakeGCS.Delay = time.Millisecond * 100 | |
| ctx := context.Background() | |
| testData := []struct { | |
| path string | |
| data string | |
| }{ | |
| { | |
| "a/b.txt", | |
| "abc", | |
| }, | |
| { | |
| "a/c/d.txt", | |
| "def", | |
| }, | |
| { | |
| "a/c/e.txt", | |
| "ghi", | |
| }, | |
| { | |
| "d.txt", | |
| "jkl", | |
| }, | |
| { | |
| "e.txt", | |
| "mno", | |
| }, | |
| } | |
| src := t.TempDir() | |
| for _, data := range testData { | |
| srcDir := path.Join(src, filepath.Dir(data.path)) | |
| err = os.MkdirAll(srcDir, 0777) | |
| if err != nil { | |
| t.Fatalf("could not make dir %s: %v", srcDir, err) | |
| } | |
| srcPath := path.Join(src, data.path) | |
| err = os.WriteFile(srcPath, []byte(data.data), 0666) | |
| if err != nil { | |
| t.Fatalf("could not write data to file %s: %v", srcPath, err) | |
| } | |
| } | |
| workers := []int{-1, 0, 1, 3, 5, 10} | |
| for _, workers := range workers { | |
| dst := t.TempDir() | |
| bucket := NewGCSBucket(fakeGCS.Client, "test", "test-bucket") | |
| defer func() { | |
| err := bucket.DeleteDir(ctx, src) | |
| if err != nil { | |
| t.Fatalf("failed to clean up %s; you will need to clean it up manually", bucket.URI(src)) | |
| } | |
| }() | |
| err = bucket.UploadDir(ctx, src, src) | |
| if err != nil { | |
| t.Fatalf("could not upload path (%v)", err) | |
| } | |
| startTime := time.Now() | |
| err = bucket.DownloadDirParallel(ctx, src, dst, workers) | |
| if err != nil { | |
| t.Fatalf("could not download dir (%v)", err) | |
| } | |
| // The time to download should be inversely proportional to the number of workers, with some overhead. | |
| duration := time.Since(startTime) | |
| maxDuration := fakeGCS.Delay | |
| if workers > 0 && workers < len(testData) { | |
| maxDuration = time.Duration(float64(fakeGCS.Delay) * float64(len(testData)) / float64(workers)) | |
| } | |
| // Try to account for the overhead of a download. | |
| maxDuration += 50 * time.Millisecond | |
| if duration > maxDuration { | |
| t.Fatalf( | |
| "parallel download with %v workers took too long; expected max duration of %v, got %v", | |
| workers, maxDuration, duration, | |
| ) | |
| } | |
| for _, data := range testData { | |
| dstPath := path.Join(dst, data.path) | |
| contents, err := os.ReadFile(dstPath) | |
| if err != nil { | |
| t.Fatalf("could not read contents for %s: %v", dstPath, err) | |
| } | |
| if !bytes.Equal(contents, []byte(data.data)) { | |
| t.Fatalf("wrong contents for %s (expected %s, got %s)", dstPath, contents, data.data) | |
| } | |
| } | |
| } | |
| } |