Skip to content

Commit 50ae4f0

Browse files
authored
Merge pull request #42 from coderbusy/copilot/optimize-resource-packaging-rules
Implement tiered compression with GZip for resource packing
2 parents 9d11488 + 446d44d commit 50ae4f0

File tree

6 files changed

+672
-27
lines changed

6 files changed

+672
-27
lines changed
Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
using Xunit;
2+
using System;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
8+
namespace LuYao.ResourcePacker.Tests
9+
{
10+
public class CompressionTests : IDisposable
11+
{
12+
private readonly string _tempDirectory;
13+
14+
public CompressionTests()
15+
{
16+
_tempDirectory = Path.Combine(Path.GetTempPath(), $"CompressionTests_{Guid.NewGuid()}");
17+
Directory.CreateDirectory(_tempDirectory);
18+
}
19+
20+
[Fact]
21+
public void SmallFile_ShouldNotBeCompressed()
22+
{
23+
// Arrange - Create a file smaller than 255 bytes
24+
var sourceDir = Path.Combine(_tempDirectory, "source");
25+
Directory.CreateDirectory(sourceDir);
26+
var smallFile = Path.Combine(sourceDir, "small.txt");
27+
var content = "Small content under 255 bytes";
28+
File.WriteAllText(smallFile, content);
29+
30+
var outputPath = Path.Combine(_tempDirectory, "test.dat");
31+
var packer = new ResourcePacker(sourceDir);
32+
33+
// Act
34+
packer.PackResources(outputPath);
35+
36+
// Assert - Read the binary format to check compression flag
37+
using var fs = new FileStream(outputPath, FileMode.Open, FileAccess.Read);
38+
using var reader = new BinaryReader(fs);
39+
40+
var version = reader.ReadByte();
41+
var count = reader.ReadInt32();
42+
var key = reader.ReadString();
43+
var originalLength = reader.ReadInt32();
44+
var storedLength = reader.ReadInt32();
45+
var isCompressed = reader.ReadBoolean();
46+
47+
Assert.False(isCompressed, "Small files (<255 bytes) should not be compressed");
48+
Assert.Equal(originalLength, storedLength);
49+
}
50+
51+
[Fact]
52+
public void MediumFile_WithGoodCompressionRatio_ShouldBeCompressed()
53+
{
54+
// Arrange - Create a file between 255 bytes and 4KB with repeating content (good compression)
55+
var sourceDir = Path.Combine(_tempDirectory, "source");
56+
Directory.CreateDirectory(sourceDir);
57+
var mediumFile = Path.Combine(sourceDir, "medium.txt");
58+
var content = new string('A', 1000); // 1000 bytes of repeated character
59+
File.WriteAllText(mediumFile, content);
60+
61+
var outputPath = Path.Combine(_tempDirectory, "test.dat");
62+
var packer = new ResourcePacker(sourceDir);
63+
64+
// Act
65+
packer.PackResources(outputPath);
66+
67+
// Assert - Read the binary format to check compression
68+
using var fs = new FileStream(outputPath, FileMode.Open, FileAccess.Read);
69+
using var reader = new BinaryReader(fs);
70+
71+
var version = reader.ReadByte();
72+
var count = reader.ReadInt32();
73+
var key = reader.ReadString();
74+
var originalLength = reader.ReadInt32();
75+
var storedLength = reader.ReadInt32();
76+
var isCompressed = reader.ReadBoolean();
77+
78+
Assert.True(isCompressed, "Medium files with good compression ratio should be compressed");
79+
Assert.True(storedLength < originalLength, "Compressed size should be smaller");
80+
81+
// Verify compression ratio is at least 5%
82+
var compressionRatio = 1.0 - ((double)storedLength / originalLength);
83+
Assert.True(compressionRatio >= 0.05, $"Compression ratio {compressionRatio:P} should be at least 5%");
84+
}
85+
86+
[Fact]
87+
public void LargeFile_WithGoodCompressionRatio_ShouldBeCompressed()
88+
{
89+
// Arrange - Create a file larger than 4KB with repeating content
90+
var sourceDir = Path.Combine(_tempDirectory, "source");
91+
Directory.CreateDirectory(sourceDir);
92+
var largeFile = Path.Combine(sourceDir, "large.txt");
93+
var content = new string('B', 10000); // 10KB of repeated character
94+
File.WriteAllText(largeFile, content);
95+
96+
var outputPath = Path.Combine(_tempDirectory, "test.dat");
97+
var packer = new ResourcePacker(sourceDir);
98+
99+
// Act
100+
packer.PackResources(outputPath);
101+
102+
// Assert
103+
using var fs = new FileStream(outputPath, FileMode.Open, FileAccess.Read);
104+
using var reader = new BinaryReader(fs);
105+
106+
var version = reader.ReadByte();
107+
var count = reader.ReadInt32();
108+
var key = reader.ReadString();
109+
var originalLength = reader.ReadInt32();
110+
var storedLength = reader.ReadInt32();
111+
var isCompressed = reader.ReadBoolean();
112+
113+
Assert.True(isCompressed, "Large files with good compression ratio should be compressed");
114+
Assert.True(storedLength < originalLength, "Compressed size should be smaller");
115+
}
116+
117+
[Fact]
118+
public void CompressedFileFormats_ShouldNotBeCompressed()
119+
{
120+
// Arrange - Create files with common compressed extensions
121+
var sourceDir = Path.Combine(_tempDirectory, "source");
122+
Directory.CreateDirectory(sourceDir);
123+
124+
var extensions = new[] { ".jpg", ".png", ".zip", ".gz" };
125+
foreach (var ext in extensions)
126+
{
127+
var filePath = Path.Combine(sourceDir, $"file{ext}");
128+
// Create a file with 1000 bytes to ensure it's above compression threshold
129+
File.WriteAllBytes(filePath, Encoding.UTF8.GetBytes(new string('X', 1000)));
130+
}
131+
132+
var outputPath = Path.Combine(_tempDirectory, "test.dat");
133+
var packer = new ResourcePacker(sourceDir);
134+
135+
// Act
136+
packer.PackResources(outputPath);
137+
138+
// Assert - All compressed format files should not be compressed
139+
using var fs = new FileStream(outputPath, FileMode.Open, FileAccess.Read);
140+
using var reader = new BinaryReader(fs);
141+
142+
var version = reader.ReadByte();
143+
var count = reader.ReadInt32();
144+
145+
for (int i = 0; i < count; i++)
146+
{
147+
var key = reader.ReadString();
148+
var originalLength = reader.ReadInt32();
149+
var storedLength = reader.ReadInt32();
150+
var isCompressed = reader.ReadBoolean();
151+
152+
Assert.False(isCompressed, $"File with key '{key}' should not be compressed (already compressed format)");
153+
}
154+
}
155+
156+
[Fact]
157+
public async Task CompressedResource_ShouldDecompressCorrectly()
158+
{
159+
// Arrange - Create a compressible file
160+
var sourceDir = Path.Combine(_tempDirectory, "source");
161+
Directory.CreateDirectory(sourceDir);
162+
var testFile = Path.Combine(sourceDir, "compressible.txt");
163+
var originalContent = new string('C', 2000); // 2KB of repeated character
164+
File.WriteAllText(testFile, originalContent);
165+
166+
var outputPath = Path.Combine(_tempDirectory, "test.dat");
167+
var packer = new ResourcePacker(sourceDir);
168+
packer.PackResources(outputPath);
169+
170+
// Act - Read the resource
171+
var packageReader = new ResourcePackageReader(outputPath);
172+
var readContent = await packageReader.ReadResourceAsStringAsync("compressible");
173+
174+
// Assert - Content should match original after decompression
175+
Assert.Equal(originalContent, readContent);
176+
}
177+
178+
[Fact]
179+
public void CompressedResource_SynchronousRead_ShouldDecompressCorrectly()
180+
{
181+
// Arrange
182+
var sourceDir = Path.Combine(_tempDirectory, "source");
183+
Directory.CreateDirectory(sourceDir);
184+
var testFile = Path.Combine(sourceDir, "sync_test.txt");
185+
var originalContent = new string('D', 3000);
186+
File.WriteAllText(testFile, originalContent);
187+
188+
var outputPath = Path.Combine(_tempDirectory, "test.dat");
189+
var packer = new ResourcePacker(sourceDir);
190+
packer.PackResources(outputPath);
191+
192+
// Act
193+
var packageReader = new ResourcePackageReader(outputPath);
194+
var readContent = packageReader.ReadResourceAsString("sync_test");
195+
196+
// Assert
197+
Assert.Equal(originalContent, readContent);
198+
}
199+
200+
[Fact]
201+
public void CompressedResource_GetStream_ShouldDecompressCorrectly()
202+
{
203+
// Arrange
204+
var sourceDir = Path.Combine(_tempDirectory, "source");
205+
Directory.CreateDirectory(sourceDir);
206+
var testFile = Path.Combine(sourceDir, "stream_test.txt");
207+
var originalContent = new string('E', 5000);
208+
File.WriteAllText(testFile, originalContent);
209+
210+
var outputPath = Path.Combine(_tempDirectory, "test.dat");
211+
var packer = new ResourcePacker(sourceDir);
212+
packer.PackResources(outputPath);
213+
214+
// Act
215+
var packageReader = new ResourcePackageReader(outputPath);
216+
using var stream = packageReader.GetStream("stream_test");
217+
using var reader = new StreamReader(stream);
218+
var readContent = reader.ReadToEnd();
219+
220+
// Assert
221+
Assert.Equal(originalContent, readContent);
222+
}
223+
224+
[Fact]
225+
public void MixedFiles_ShouldCompressSelectively()
226+
{
227+
// Arrange - Create a mix of files
228+
var sourceDir = Path.Combine(_tempDirectory, "source");
229+
Directory.CreateDirectory(sourceDir);
230+
231+
// Small file (should not compress)
232+
File.WriteAllText(Path.Combine(sourceDir, "tiny.txt"), "Small");
233+
234+
// Compressible medium file
235+
File.WriteAllText(Path.Combine(sourceDir, "medium.txt"), new string('M', 1000));
236+
237+
// Already compressed format
238+
File.WriteAllBytes(Path.Combine(sourceDir, "image.png"), Encoding.UTF8.GetBytes(new string('I', 1000)));
239+
240+
// Large compressible file
241+
File.WriteAllText(Path.Combine(sourceDir, "large.txt"), new string('L', 10000));
242+
243+
var outputPath = Path.Combine(_tempDirectory, "test.dat");
244+
var packer = new ResourcePacker(sourceDir);
245+
246+
// Act
247+
packer.PackResources(outputPath);
248+
249+
// Assert - Verify compression decisions
250+
var packageReader = new ResourcePackageReader(outputPath);
251+
252+
// All files should be readable
253+
Assert.True(packageReader.ContainsKey("tiny"));
254+
Assert.True(packageReader.ContainsKey("medium"));
255+
Assert.True(packageReader.ContainsKey("image"));
256+
Assert.True(packageReader.ContainsKey("large"));
257+
258+
// Verify content integrity
259+
Assert.Equal("Small", packageReader.ReadResourceAsString("tiny"));
260+
Assert.Equal(new string('M', 1000), packageReader.ReadResourceAsString("medium"));
261+
Assert.Equal(new string('I', 1000), packageReader.ReadResourceAsString("image"));
262+
Assert.Equal(new string('L', 10000), packageReader.ReadResourceAsString("large"));
263+
}
264+
265+
[Fact]
266+
public void Edge_ExactlyAtThresholds_ShouldHandleCorrectly()
267+
{
268+
// Arrange - Create files at exact threshold boundaries
269+
var sourceDir = Path.Combine(_tempDirectory, "source");
270+
Directory.CreateDirectory(sourceDir);
271+
272+
// Exactly 254 bytes (just below threshold - should not compress)
273+
File.WriteAllBytes(Path.Combine(sourceDir, "at254.txt"), new byte[254]);
274+
275+
// Exactly 255 bytes (at threshold - should evaluate for compression)
276+
File.WriteAllBytes(Path.Combine(sourceDir, "at255.txt"), Encoding.UTF8.GetBytes(new string('A', 255)));
277+
278+
// Exactly 4KB (at threshold - should sample)
279+
File.WriteAllBytes(Path.Combine(sourceDir, "at4kb.txt"), Encoding.UTF8.GetBytes(new string('B', 4096)));
280+
281+
var outputPath = Path.Combine(_tempDirectory, "test.dat");
282+
var packer = new ResourcePacker(sourceDir);
283+
284+
// Act
285+
packer.PackResources(outputPath);
286+
287+
// Assert - All files should be readable
288+
var packageReader = new ResourcePackageReader(outputPath);
289+
Assert.True(packageReader.ContainsKey("at254"));
290+
Assert.True(packageReader.ContainsKey("at255"));
291+
Assert.True(packageReader.ContainsKey("at4kb"));
292+
}
293+
294+
public void Dispose()
295+
{
296+
if (Directory.Exists(_tempDirectory))
297+
{
298+
Directory.Delete(_tempDirectory, true);
299+
}
300+
}
301+
}
302+
}

LuYao.ResourcePacker.Tests/ResourcePackerTests.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,14 +152,17 @@ public void PackedFile_ShouldHaveCorrectFormat()
152152
var count = reader.ReadInt32();
153153
Assert.True(count > 0, "Should have at least one resource");
154154

155-
// Read index entries - should be sorted
155+
// Read index entries - should be sorted and include compression metadata
156156
var keys = new System.Collections.Generic.List<string>();
157157
for (int i = 0; i < count; i++)
158158
{
159159
var key = reader.ReadString();
160-
var length = reader.ReadInt32();
160+
var originalLength = reader.ReadInt32();
161+
var storedLength = reader.ReadInt32();
162+
var isCompressed = reader.ReadBoolean();
161163
keys.Add(key);
162-
Assert.True(length > 0, $"Resource '{key}' should have positive length");
164+
Assert.True(originalLength > 0, $"Resource '{key}' should have positive original length");
165+
Assert.True(storedLength > 0, $"Resource '{key}' should have positive stored length");
163166
}
164167

165168
// Verify keys are sorted

0 commit comments

Comments
 (0)