Skip to content

Commit 4cb36a6

Browse files
committed
Use golden vbaProject.bin file for testing
1 parent e94476a commit 4cb36a6

File tree

3 files changed

+131
-0
lines changed

3 files changed

+131
-0
lines changed

tests/VbaCompiler.Tests/Streams/VbaProjectRoundTripTests.cs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
using System.Collections.Generic;
66
using System.IO;
77
using System.Linq;
8+
using System.Security.Cryptography;
89
using System.Text;
910
using Kavod.Vba.Compression;
1011
using OpenMcdf;
12+
using vbamc.Vba;
1113
using VbadDecompiler = vbad;
1214

1315
namespace vbamc.Tests.Streams
@@ -225,5 +227,131 @@ public void CompileVbaProject_WithMultipleModules_AllShouldMatchOriginalSource()
225227
$"Decompiled source for module '{module.Name}' should exactly match the original source with VBA headers");
226228
}
227229
}
230+
231+
[Test]
232+
public void CompileVbaProject_ShouldMatchGoldenFile()
233+
{
234+
// Use fixed values to ensure deterministic output
235+
var fixedGuid = new Guid("12345678-1234-1234-1234-123456789ABC");
236+
var originalRandomGenerator = VbaEncryption.CreateRandomGenerator;
237+
238+
try
239+
{
240+
// Replace random generator with deterministic one
241+
VbaEncryption.CreateRandomGenerator = () => new DeterministicRandomNumberGenerator();
242+
243+
var sourcePath = Path.Combine(TestContext.CurrentContext.TestDirectory, "data");
244+
var modulePath = Path.Combine(sourcePath, "TestModule.vb");
245+
246+
var compiler = new VbaCompiler
247+
{
248+
ProjectId = fixedGuid,
249+
ProjectName = "GoldenTest",
250+
ProjectVersion = "1.0.0",
251+
CompanyName = "TestCompany"
252+
};
253+
compiler.AddModule(modulePath);
254+
255+
using var stream = compiler.CompileVbaProject();
256+
var compiledBytes = stream.ToArray();
257+
258+
// Mask out compound file timestamps that vary between runs
259+
// These are at offsets in the directory entries (varies by file structure)
260+
MaskCompoundFileTimestamps(compiledBytes);
261+
262+
var goldenFilePath = Path.Combine(sourcePath, "vbaProject.bin");
263+
264+
if (!File.Exists(goldenFilePath))
265+
{
266+
// Generate golden file if it doesn't exist
267+
File.WriteAllBytes(goldenFilePath, compiledBytes);
268+
Assert.Inconclusive($"Golden file created at {goldenFilePath}. Re-run the test to validate.");
269+
}
270+
271+
var goldenBytes = File.ReadAllBytes(goldenFilePath);
272+
MaskCompoundFileTimestamps(goldenBytes);
273+
274+
ClassicAssert.AreEqual(goldenBytes, compiledBytes,
275+
"Compiled VBA project should match the golden file byte-for-byte (excluding timestamps)");
276+
}
277+
finally
278+
{
279+
// Restore original random generator
280+
VbaEncryption.CreateRandomGenerator = originalRandomGenerator;
281+
}
282+
}
283+
284+
/// <summary>
285+
/// Masks out timestamp fields in compound file directory entries.
286+
/// Compound files have 128-byte directory entries with timestamps at specific offsets.
287+
/// </summary>
288+
private static void MaskCompoundFileTimestamps(byte[] data)
289+
{
290+
// Compound file directory entries start at sector 0 (offset 512) or later
291+
// Each entry is 128 bytes with creation time at offset 100 and modification time at offset 108
292+
// The directory sector location is specified in the header at offset 48
293+
294+
if (data.Length < 512)
295+
return;
296+
297+
// Read directory sector location from header (offset 48, 4 bytes, little-endian)
298+
int directorySectorIndex = BitConverter.ToInt32(data, 48);
299+
if (directorySectorIndex < 0)
300+
return;
301+
302+
// Sector size is typically 512 bytes for compound files with version 3
303+
const int sectorSize = 512;
304+
const int headerSize = 512;
305+
int directoryOffset = headerSize + (directorySectorIndex * sectorSize);
306+
307+
// Mask timestamps in all directory entries
308+
const int entrySize = 128;
309+
const int creationTimeOffset = 100;
310+
const int modificationTimeOffset = 108;
311+
const int timestampSize = 8;
312+
313+
for (int entryStart = directoryOffset;
314+
entryStart + entrySize <= data.Length;
315+
entryStart += entrySize)
316+
{
317+
// Check if this looks like a valid entry (first byte of name should be non-zero for used entries)
318+
if (data[entryStart] == 0 && data[entryStart + 1] == 0)
319+
break; // End of entries
320+
321+
// Mask creation time
322+
for (int i = 0; i < timestampSize; i++)
323+
data[entryStart + creationTimeOffset + i] = 0;
324+
325+
// Mask modification time
326+
for (int i = 0; i < timestampSize; i++)
327+
data[entryStart + modificationTimeOffset + i] = 0;
328+
}
329+
}
330+
331+
/// <summary>
332+
/// A deterministic random number generator for testing purposes.
333+
/// Produces a fixed repeatable sequence independent of .NET version.
334+
/// </summary>
335+
private sealed class DeterministicRandomNumberGenerator : RandomNumberGenerator
336+
{
337+
private int _index;
338+
339+
public override void GetBytes(byte[] data)
340+
{
341+
for (int i = 0; i < data.Length; i++)
342+
{
343+
data[i] = (byte)((_index++ * 17 + 31) % 256);
344+
}
345+
}
346+
347+
public override void GetNonZeroBytes(byte[] data)
348+
{
349+
for (int i = 0; i < data.Length; i++)
350+
{
351+
// Generate values 1-255 (never zero)
352+
data[i] = (byte)((_index++ * 17 + 31) % 255 + 1);
353+
}
354+
}
355+
}
228356
}
229357
}

tests/VbaCompiler.Tests/VbaCompiler.Tests.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
<Content Include="data\TestModule.vb">
2424
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
2525
</Content>
26+
<Content Include="data\vbaProject.bin" Condition="Exists('data\vbaProject.bin')">
27+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
28+
</Content>
2629
</ItemGroup>
2730

2831
<ItemGroup>
4 KB
Binary file not shown.

0 commit comments

Comments
 (0)