|
1 | 1 | package zipserver |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "archive/zip" |
| 5 | + "bytes" |
4 | 6 | "context" |
5 | 7 | "crypto/md5" |
6 | 8 | "fmt" |
@@ -92,3 +94,83 @@ func TestPutAndDeleteFile(t *testing.T) { |
92 | 94 | } |
93 | 95 | }) |
94 | 96 | } |
| 97 | + |
| 98 | +func TestGetReaderAtRangeEfficiency(t *testing.T) { |
| 99 | + ctx := context.Background() |
| 100 | + |
| 101 | + withGoogleCloudStorage(t, func(storage Storage, config *Config) { |
| 102 | + // Create a zip with uncompressed data to make it large enough to see efficiency gains |
| 103 | + var buf bytes.Buffer |
| 104 | + zw := zip.NewWriter(&buf) |
| 105 | + |
| 106 | + // Add files with Store (no compression) to ensure predictable size |
| 107 | + for i := 0; i < 10; i++ { |
| 108 | + header := &zip.FileHeader{ |
| 109 | + Name: fmt.Sprintf("file%d.bin", i), |
| 110 | + Method: zip.Store, // No compression |
| 111 | + } |
| 112 | + f, err := zw.CreateHeader(header) |
| 113 | + if err != nil { |
| 114 | + t.Fatalf("create file: %v", err) |
| 115 | + } |
| 116 | + // Write 100KB of pseudo-random data per file (1MB total) |
| 117 | + padding := make([]byte, 100*1024) |
| 118 | + for j := range padding { |
| 119 | + padding[j] = byte((j * 17) % 256) // Pseudo-random pattern |
| 120 | + } |
| 121 | + if _, err := f.Write(padding); err != nil { |
| 122 | + t.Fatalf("write: %v", err) |
| 123 | + } |
| 124 | + } |
| 125 | + if err := zw.Close(); err != nil { |
| 126 | + t.Fatalf("close zip: %v", err) |
| 127 | + } |
| 128 | + |
| 129 | + zipData := buf.Bytes() |
| 130 | + zipSize := int64(len(zipData)) |
| 131 | + t.Logf("Test zip size: %d bytes", zipSize) |
| 132 | + |
| 133 | + // Upload the test zip |
| 134 | + testKey := "zipserver_range_test.zip" |
| 135 | + _, err := storage.PutFile(ctx, config.Bucket, testKey, bytes.NewReader(zipData), PutOptions{ |
| 136 | + ContentType: "application/zip", |
| 137 | + }) |
| 138 | + if err != nil { |
| 139 | + t.Fatalf("upload test zip: %v", err) |
| 140 | + } |
| 141 | + defer storage.DeleteFile(ctx, config.Bucket, testKey) |
| 142 | + |
| 143 | + // Get a ReaderAt and list the zip contents |
| 144 | + readerAt, size, err := storage.GetReaderAt(ctx, config.Bucket, testKey, 0) |
| 145 | + if err != nil { |
| 146 | + t.Fatalf("GetReaderAt: %v", err) |
| 147 | + } |
| 148 | + defer readerAt.Close() |
| 149 | + |
| 150 | + if size != zipSize { |
| 151 | + t.Fatalf("size mismatch: got %d, expected %d", size, zipSize) |
| 152 | + } |
| 153 | + |
| 154 | + // Use zip.NewReader which should only read the central directory |
| 155 | + zipReader, err := zip.NewReader(readerAt, size) |
| 156 | + if err != nil { |
| 157 | + t.Fatalf("zip.NewReader: %v", err) |
| 158 | + } |
| 159 | + |
| 160 | + // Verify we got the right files |
| 161 | + if len(zipReader.File) != 10 { |
| 162 | + t.Fatalf("expected 10 files, got %d", len(zipReader.File)) |
| 163 | + } |
| 164 | + |
| 165 | + bytesRead := readerAt.BytesRead() |
| 166 | + t.Logf("Bytes read: %d / %d (%.2f%%)", bytesRead, zipSize, float64(bytesRead)/float64(zipSize)*100) |
| 167 | + |
| 168 | + // The central directory + EOCD should be much smaller than the full zip |
| 169 | + // For a 1MB zip with 10 files, we expect to read only a few KB |
| 170 | + // Use 5% as threshold - actual should be < 1% |
| 171 | + maxExpectedBytes := uint64(zipSize / 20) |
| 172 | + if bytesRead > maxExpectedBytes { |
| 173 | + t.Errorf("Read too many bytes: %d > %d (expected < 5%% of file)", bytesRead, maxExpectedBytes) |
| 174 | + } |
| 175 | + }) |
| 176 | +} |
0 commit comments