diff --git a/tests/SimpleBranchVersioning.Tests/AppVersionGeneratorIntegrationTests.cs b/tests/SimpleBranchVersioning.Tests/AppVersionGeneratorIntegrationTests.cs
new file mode 100644
index 0000000..21a26bf
--- /dev/null
+++ b/tests/SimpleBranchVersioning.Tests/AppVersionGeneratorIntegrationTests.cs
@@ -0,0 +1,256 @@
+using SimpleBranchVersioning.Tests.Helpers;
+
+namespace SimpleBranchVersioning.Tests;
+
+///
+/// Integration tests for AppVersionGenerator that test real file I/O paths.
+/// These tests create actual git directory structures on disk.
+///
+public class AppVersionGeneratorIntegrationTests
+{
+ private const string MinimalSource = """
+ namespace TestApp;
+ public class Program { }
+ """;
+
+ #region ParseGitInfo Integration Tests
+
+ [Test]
+ public async Task Generator_WithRealGitDirectory_ReadsCommitFromRefsHeads()
+ {
+ // Arrange
+ string tempDir = CreateTempDirectory();
+ try
+ {
+ string gitDir = Path.Combine(tempDir, ".git");
+ Directory.CreateDirectory(gitDir);
+ Directory.CreateDirectory(Path.Combine(gitDir, "refs", "heads"));
+
+ // Create HEAD file pointing to a branch
+ string headPath = Path.Combine(gitDir, "HEAD");
+ await File.WriteAllTextAsync(headPath, "ref: refs/heads/main\n");
+
+ // Create refs/heads/main with a commit hash
+ string refPath = Path.Combine(gitDir, "refs", "heads", "main");
+ await File.WriteAllTextAsync(refPath, "abc1234def5678901234567890123456789012345\n");
+
+ // Act
+ var result = GeneratorTestHelper.RunGeneratorWithGitDirectory(
+ MinimalSource,
+ headPath);
+
+ string generatedSource = result.GetRequiredGeneratedSource("AppVersion.g.cs");
+
+ // Assert
+ await Assert.That(generatedSource).Contains("""Branch = "main""");
+ await Assert.That(generatedSource).Contains("""CommitId = "abc1234""");
+ }
+ finally
+ {
+ DeleteTempDirectory(tempDir);
+ }
+ }
+
+ [Test]
+ public async Task Generator_WithRealGitDirectory_ReadsCommitFromNestedBranch()
+ {
+ // Arrange
+ string tempDir = CreateTempDirectory();
+ try
+ {
+ string gitDir = Path.Combine(tempDir, ".git");
+ Directory.CreateDirectory(gitDir);
+ Directory.CreateDirectory(Path.Combine(gitDir, "refs", "heads", "feature"));
+
+ // Create HEAD file pointing to a nested branch
+ string headPath = Path.Combine(gitDir, "HEAD");
+ await File.WriteAllTextAsync(headPath, "ref: refs/heads/feature/login\n");
+
+ // Create refs/heads/feature/login with a commit hash
+ string refPath = Path.Combine(gitDir, "refs", "heads", "feature", "login");
+ await File.WriteAllTextAsync(refPath, "def5678abc1234567890123456789012345678901\n");
+
+ // Act
+ var result = GeneratorTestHelper.RunGeneratorWithGitDirectory(
+ MinimalSource,
+ headPath);
+
+ string generatedSource = result.GetRequiredGeneratedSource("AppVersion.g.cs");
+
+ // Assert
+ await Assert.That(generatedSource).Contains("""Branch = "feature/login""");
+ await Assert.That(generatedSource).Contains("""CommitId = "def5678""");
+ }
+ finally
+ {
+ DeleteTempDirectory(tempDir);
+ }
+ }
+
+ [Test]
+ public async Task Generator_WithRealGitDirectory_HandlesDetachedHead()
+ {
+ // Arrange
+ string tempDir = CreateTempDirectory();
+ try
+ {
+ string gitDir = Path.Combine(tempDir, ".git");
+ Directory.CreateDirectory(gitDir);
+
+ // Create HEAD file with a direct commit hash (detached HEAD)
+ string headPath = Path.Combine(gitDir, "HEAD");
+ await File.WriteAllTextAsync(headPath, "9876543210abcdef1234567890abcdef12345678\n");
+
+ // Act
+ var result = GeneratorTestHelper.RunGeneratorWithGitDirectory(
+ MinimalSource,
+ headPath);
+
+ string generatedSource = result.GetRequiredGeneratedSource("AppVersion.g.cs");
+
+ // Assert
+ await Assert.That(generatedSource).Contains("""Branch = "detached""");
+ await Assert.That(generatedSource).Contains("""CommitId = "9876543""");
+ }
+ finally
+ {
+ DeleteTempDirectory(tempDir);
+ }
+ }
+
+ [Test]
+ public async Task Generator_WithRealGitDirectory_MissingRefFile_UsesBranchWithoutCommitId()
+ {
+ // Arrange
+ string tempDir = CreateTempDirectory();
+ try
+ {
+ string gitDir = Path.Combine(tempDir, ".git");
+ Directory.CreateDirectory(gitDir);
+ Directory.CreateDirectory(Path.Combine(gitDir, "refs", "heads"));
+
+ // Create HEAD file pointing to a branch
+ string headPath = Path.Combine(gitDir, "HEAD");
+ await File.WriteAllTextAsync(headPath, "ref: refs/heads/main\n");
+
+ // Note: NOT creating refs/heads/main file - simulates missing ref
+
+ // Act
+ var result = GeneratorTestHelper.RunGeneratorWithGitDirectory(
+ MinimalSource,
+ headPath);
+
+ string generatedSource = result.GetRequiredGeneratedSource("AppVersion.g.cs");
+
+ // Assert - branch is extracted but commit ID falls back to "0000000"
+ await Assert.That(generatedSource).Contains("""Branch = "main""");
+ await Assert.That(generatedSource).Contains("""CommitId = "0000000""");
+ }
+ finally
+ {
+ DeleteTempDirectory(tempDir);
+ }
+ }
+
+ [Test]
+ public async Task Generator_WithRealGitDirectory_ShortCommitHash_UsesBranchWithoutCommitId()
+ {
+ // Arrange
+ string tempDir = CreateTempDirectory();
+ try
+ {
+ string gitDir = Path.Combine(tempDir, ".git");
+ Directory.CreateDirectory(gitDir);
+ Directory.CreateDirectory(Path.Combine(gitDir, "refs", "heads"));
+
+ // Create HEAD file pointing to a branch
+ string headPath = Path.Combine(gitDir, "HEAD");
+ await File.WriteAllTextAsync(headPath, "ref: refs/heads/main\n");
+
+ // Create refs/heads/main with a too-short commit hash (< 7 chars)
+ string refPath = Path.Combine(gitDir, "refs", "heads", "main");
+ await File.WriteAllTextAsync(refPath, "abc12\n");
+
+ // Act
+ var result = GeneratorTestHelper.RunGeneratorWithGitDirectory(
+ MinimalSource,
+ headPath);
+
+ string generatedSource = result.GetRequiredGeneratedSource("AppVersion.g.cs");
+
+ // Assert - branch is extracted but commit ID falls back to "0000000" due to short hash
+ await Assert.That(generatedSource).Contains("""Branch = "main""");
+ await Assert.That(generatedSource).Contains("""CommitId = "0000000""");
+ }
+ finally
+ {
+ DeleteTempDirectory(tempDir);
+ }
+ }
+
+ [Test]
+ public async Task Generator_WithRealGitDirectory_ReleaseBranch_ExtractsVersion()
+ {
+ // Arrange
+ string tempDir = CreateTempDirectory();
+ try
+ {
+ string gitDir = Path.Combine(tempDir, ".git");
+ Directory.CreateDirectory(gitDir);
+ Directory.CreateDirectory(Path.Combine(gitDir, "refs", "heads", "release"));
+
+ // Create HEAD file pointing to a release branch
+ string headPath = Path.Combine(gitDir, "HEAD");
+ await File.WriteAllTextAsync(headPath, "ref: refs/heads/release/v1.2.3\n");
+
+ // Create refs/heads/release/v1.2.3 with a commit hash
+ string refPath = Path.Combine(gitDir, "refs", "heads", "release", "v1.2.3");
+ await File.WriteAllTextAsync(refPath, "abc1234def5678901234567890123456789012345\n");
+
+ // Act
+ var result = GeneratorTestHelper.RunGeneratorWithGitDirectory(
+ MinimalSource,
+ headPath);
+
+ string generatedSource = result.GetRequiredGeneratedSource("AppVersion.g.cs");
+
+ // Assert
+ await Assert.That(generatedSource).Contains("""Branch = "release/v1.2.3""");
+ await Assert.That(generatedSource).Contains("""Version = "1.2.3""");
+ await Assert.That(generatedSource).Contains("""CommitId = "abc1234""");
+ await Assert.That(generatedSource).Contains("""PackageVersion = "1.2.3+abc1234""");
+ }
+ finally
+ {
+ DeleteTempDirectory(tempDir);
+ }
+ }
+
+ #endregion
+
+ #region Helper Methods
+
+ private static string CreateTempDirectory()
+ {
+ string tempDir = Path.Combine(Path.GetTempPath(), $"sbv-test-{Guid.NewGuid():N}");
+ Directory.CreateDirectory(tempDir);
+ return tempDir;
+ }
+
+ private static void DeleteTempDirectory(string tempDir)
+ {
+ try
+ {
+ if (Directory.Exists(tempDir))
+ {
+ Directory.Delete(tempDir, recursive: true);
+ }
+ }
+ catch
+ {
+ // Ignore cleanup errors in tests
+ }
+ }
+
+ #endregion
+}
diff --git a/tests/SimpleBranchVersioning.Tests/Helpers/FileBasedAdditionalText.cs b/tests/SimpleBranchVersioning.Tests/Helpers/FileBasedAdditionalText.cs
new file mode 100644
index 0000000..b19ee75
--- /dev/null
+++ b/tests/SimpleBranchVersioning.Tests/Helpers/FileBasedAdditionalText.cs
@@ -0,0 +1,24 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Text;
+
+namespace SimpleBranchVersioning.Tests.Helpers;
+
+///
+/// File-based implementation of AdditionalText for integration tests.
+/// Reads content from actual files on disk.
+///
+public sealed class FileBasedAdditionalText(string filePath) : AdditionalText
+{
+ public override string Path => filePath;
+
+ public override SourceText? GetText(CancellationToken cancellationToken = default)
+ {
+ if (!File.Exists(filePath))
+ {
+ return null;
+ }
+
+ string content = File.ReadAllText(filePath);
+ return SourceText.From(content);
+ }
+}
diff --git a/tests/SimpleBranchVersioning.Tests/Helpers/GeneratorTestHelper.cs b/tests/SimpleBranchVersioning.Tests/Helpers/GeneratorTestHelper.cs
index 0d4f480..34d0f99 100644
--- a/tests/SimpleBranchVersioning.Tests/Helpers/GeneratorTestHelper.cs
+++ b/tests/SimpleBranchVersioning.Tests/Helpers/GeneratorTestHelper.cs
@@ -63,6 +63,56 @@ public static GeneratorTestResult RunGenerator(
driver.GetRunResult());
}
+ ///
+ /// Runs the AppVersionGenerator with a real git directory structure for integration testing.
+ /// This allows testing the file I/O paths in ParseGitInfo.
+ ///
+ public static GeneratorTestResult RunGeneratorWithGitDirectory(
+ string sourceCode,
+ string gitHeadFilePath,
+ string? rootNamespace = null,
+ bool includeCommitIdMetadata = true,
+ bool generateVersionFile = false,
+ bool setPackageVersionFromBranch = true)
+ {
+ var syntaxTree = CSharpSyntaxTree.ParseText(sourceCode);
+
+ // Use Basic.Reference.Assemblies for compilation references
+ var compilation = CSharpCompilation.Create(
+ assemblyName: "TestAssembly",
+ syntaxTrees: [syntaxTree],
+ references: Basic.Reference.Assemblies.Net80.References.All,
+ options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
+
+ // Build editorconfig content for global options
+ string editorConfig = BuildEditorConfig(
+ branchOverride: null,
+ rootNamespace,
+ includeCommitIdMetadata,
+ generateVersionFile,
+ setPackageVersionFromBranch);
+
+ // Create additional texts using the real file path
+ ImmutableArray additionalTexts = [new FileBasedAdditionalText(gitHeadFilePath)];
+
+ // Configure driver with options
+ GeneratorDriver driver = CSharpGeneratorDriver.Create(
+ generators: [new AppVersionGenerator().AsSourceGenerator()],
+ additionalTexts: additionalTexts,
+ parseOptions: (CSharpParseOptions)syntaxTree.Options,
+ optionsProvider: new TestAnalyzerConfigOptionsProvider(editorConfig));
+
+ driver = driver.RunGeneratorsAndUpdateCompilation(
+ compilation,
+ out var outputCompilation,
+ out var diagnostics);
+
+ return new GeneratorTestResult(
+ outputCompilation,
+ diagnostics,
+ driver.GetRunResult());
+ }
+
private static string BuildEditorConfig(
string? branchOverride,
string? rootNamespace,