Skip to content

Commit 83f4186

Browse files
authored
Merge pull request #18 from coderbusy/copilot/ensure-thread-safe-resource-package-reader
Make ResourcePackageReader thread-safe using FileShare.Read
2 parents e39d79d + f7d1483 commit 83f4186

File tree

4 files changed

+347
-140
lines changed

4 files changed

+347
-140
lines changed
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
using Xunit;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.IO;
5+
using System.Linq;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
9+
namespace LuYao.ResourcePacker.Tests
10+
{
11+
public class ResourcePackageReaderThreadSafetyTests : IDisposable
12+
{
13+
private readonly string _tempDirectory;
14+
private readonly string _outputPath;
15+
16+
public ResourcePackageReaderThreadSafetyTests()
17+
{
18+
// Create temporary directory for tests
19+
_tempDirectory = Path.Combine(Path.GetTempPath(), $"ResourcePackerThreadSafetyTests_{Guid.NewGuid()}");
20+
Directory.CreateDirectory(_tempDirectory);
21+
_outputPath = Path.Combine(_tempDirectory, "test.dat");
22+
23+
// Create test resource package
24+
var sourceDir = Path.Combine(Directory.GetCurrentDirectory(), "TestResources");
25+
var packer = new ResourcePacker(sourceDir, "*.res.*");
26+
packer.PackResources(_outputPath);
27+
}
28+
29+
[Fact]
30+
public async Task ConcurrentReadResourceAsync_ShouldNotCorruptData()
31+
{
32+
// Arrange
33+
var reader = new ResourcePackageReader(_outputPath);
34+
const int threadCount = 10;
35+
const int iterationsPerThread = 50;
36+
37+
// Act - Read the same resources concurrently from multiple threads
38+
var tasks = Enumerable.Range(0, threadCount).Select(async _ =>
39+
{
40+
for (int i = 0; i < iterationsPerThread; i++)
41+
{
42+
var testContent = await reader.ReadResourceAsStringAsync("test");
43+
var greetingContent = await reader.ReadResourceAsStringAsync("greeting");
44+
45+
// Assert - Verify data integrity
46+
Assert.Contains("Hello, World!", testContent);
47+
Assert.Contains("Hello from resource file!", greetingContent);
48+
}
49+
}).ToArray();
50+
51+
await Task.WhenAll(tasks);
52+
}
53+
54+
[Fact]
55+
public void ConcurrentReadResource_ShouldNotCorruptData()
56+
{
57+
// Arrange
58+
var reader = new ResourcePackageReader(_outputPath);
59+
const int threadCount = 10;
60+
const int iterationsPerThread = 50;
61+
62+
// Act - Read the same resources concurrently from multiple threads
63+
var tasks = Enumerable.Range(0, threadCount).Select(i => Task.Run(() =>
64+
{
65+
for (int j = 0; j < iterationsPerThread; j++)
66+
{
67+
var testContent = reader.ReadResourceAsString("test");
68+
var greetingContent = reader.ReadResourceAsString("greeting");
69+
70+
// Assert - Verify data integrity
71+
Assert.Contains("Hello, World!", testContent);
72+
Assert.Contains("Hello from resource file!", greetingContent);
73+
}
74+
})).ToArray();
75+
76+
Task.WaitAll(tasks);
77+
}
78+
79+
[Fact]
80+
public void ConcurrentReadResourceBytes_ShouldReturnCorrectData()
81+
{
82+
// Arrange
83+
var reader = new ResourcePackageReader(_outputPath);
84+
const int threadCount = 10;
85+
const int iterationsPerThread = 50;
86+
87+
// Act - Read the same resources concurrently from multiple threads
88+
var tasks = Enumerable.Range(0, threadCount).Select(i => Task.Run(() =>
89+
{
90+
for (int j = 0; j < iterationsPerThread; j++)
91+
{
92+
var testBytes = reader.ReadResource("test");
93+
var greetingBytes = reader.ReadResource("greeting");
94+
95+
// Assert - Verify data integrity by converting to string
96+
var testContent = Encoding.UTF8.GetString(testBytes);
97+
var greetingContent = Encoding.UTF8.GetString(greetingBytes);
98+
99+
Assert.Contains("Hello, World!", testContent);
100+
Assert.Contains("Hello from resource file!", greetingContent);
101+
}
102+
})).ToArray();
103+
104+
Task.WaitAll(tasks);
105+
}
106+
107+
[Fact]
108+
public void ConcurrentGetStream_ShouldNotCorruptData()
109+
{
110+
// Arrange
111+
var reader = new ResourcePackageReader(_outputPath);
112+
const int threadCount = 10;
113+
const int iterationsPerThread = 20;
114+
115+
// Act - Get streams concurrently from multiple threads
116+
var tasks = Enumerable.Range(0, threadCount).Select(i => Task.Run(() =>
117+
{
118+
for (int j = 0; j < iterationsPerThread; j++)
119+
{
120+
using var testStream = reader.GetStream("test");
121+
using var greetingStream = reader.GetStream("greeting");
122+
123+
using var testReader = new StreamReader(testStream);
124+
using var greetingReader = new StreamReader(greetingStream);
125+
126+
var testContent = testReader.ReadToEnd();
127+
var greetingContent = greetingReader.ReadToEnd();
128+
129+
// Assert - Verify data integrity
130+
Assert.Contains("Hello, World!", testContent);
131+
Assert.Contains("Hello from resource file!", greetingContent);
132+
}
133+
})).ToArray();
134+
135+
Task.WaitAll(tasks);
136+
}
137+
138+
[Fact]
139+
public void MixedConcurrentOperations_ShouldNotCorruptData()
140+
{
141+
// Arrange
142+
var reader = new ResourcePackageReader(_outputPath);
143+
const int threadCount = 15;
144+
const int iterationsPerThread = 30;
145+
146+
// Act - Mix different read operations concurrently
147+
var tasks = Enumerable.Range(0, threadCount).Select(i => Task.Run(() =>
148+
{
149+
for (int j = 0; j < iterationsPerThread; j++)
150+
{
151+
// Alternate between different read methods
152+
switch (j % 3)
153+
{
154+
case 0:
155+
var bytes = reader.ReadResource("test");
156+
Assert.True(bytes.Length > 0);
157+
break;
158+
case 1:
159+
var content = reader.ReadResourceAsString("greeting");
160+
Assert.Contains("Hello from resource file!", content);
161+
break;
162+
case 2:
163+
using (var stream = reader.GetStream("test"))
164+
{
165+
using var sr = new StreamReader(stream);
166+
var streamContent = sr.ReadToEnd();
167+
Assert.Contains("Hello, World!", streamContent);
168+
}
169+
break;
170+
}
171+
}
172+
})).ToArray();
173+
174+
Task.WaitAll(tasks);
175+
}
176+
177+
[Fact]
178+
public async Task ConcurrentReadWithDifferentEncodings_ShouldNotCorruptData()
179+
{
180+
// Arrange
181+
var reader = new ResourcePackageReader(_outputPath);
182+
const int threadCount = 8;
183+
const int iterationsPerThread = 40;
184+
185+
// Act - Read with different encodings concurrently
186+
var tasks = Enumerable.Range(0, threadCount).Select(async i =>
187+
{
188+
for (int j = 0; j < iterationsPerThread; j++)
189+
{
190+
var encoding = (j % 2 == 0) ? Encoding.UTF8 : Encoding.ASCII;
191+
var content = await reader.ReadResourceAsStringAsync("test", encoding);
192+
193+
// Assert - Verify data integrity
194+
Assert.Contains("Hello, World!", content);
195+
}
196+
}).ToArray();
197+
198+
await Task.WhenAll(tasks);
199+
}
200+
201+
[Fact]
202+
public void ConcurrentStreamReads_ShouldNotCorruptData()
203+
{
204+
// Arrange
205+
var reader = new ResourcePackageReader(_outputPath);
206+
const int threadCount = 10;
207+
208+
// Act - Multiple threads reading from streams simultaneously
209+
var tasks = Enumerable.Range(0, threadCount).Select(i => Task.Run(() =>
210+
{
211+
using var stream = reader.GetStream("test");
212+
var buffer = new byte[1024];
213+
var totalRead = 0;
214+
215+
while (true)
216+
{
217+
var bytesRead = stream.Read(buffer, totalRead, buffer.Length - totalRead);
218+
if (bytesRead == 0)
219+
break;
220+
totalRead += bytesRead;
221+
}
222+
223+
var content = Encoding.UTF8.GetString(buffer, 0, totalRead);
224+
225+
// Assert - Verify data integrity
226+
Assert.Contains("Hello, World!", content);
227+
})).ToArray();
228+
229+
Task.WaitAll(tasks);
230+
}
231+
232+
public void Dispose()
233+
{
234+
// Clean up temporary directory
235+
if (Directory.Exists(_tempDirectory))
236+
{
237+
Directory.Delete(_tempDirectory, true);
238+
}
239+
}
240+
}
241+
}

0 commit comments

Comments
 (0)