diff --git a/.github/workflows/dotnet-integration.yml b/.github/workflows/dotnet-integration.yml
new file mode 100644
index 00000000..5bd5a8f9
--- /dev/null
+++ b/.github/workflows/dotnet-integration.yml
@@ -0,0 +1,32 @@
+name: Integration Tests
+
+on:
+ workflow_dispatch:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ build:
+
+ runs-on: ubuntu-20.04
+
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ fetch-depth: 0 # depth is needed for nbgv
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: |
+ 6.0.x
+ 8.0.x
+ include-prerelease: true
+
+ - name: Build
+ run: dotnet build Dapper.AOT.sln -c Debug
+
+ - name: E2E Tests
+ run: dotnet test test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj --no-build --verbosity normal -c Debug
\ No newline at end of file
diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index d3140df0..a45009ae 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -13,40 +13,40 @@ jobs:
runs-on: windows-latest
steps:
- - uses: actions/checkout@v2
- with:
- fetch-depth: 0 # depth is needed for nbgv
-
- - name: Setup .NET
- uses: actions/setup-dotnet@v1
- with:
- dotnet-version: |
- 6.0.x
- 8.0.x
- include-prerelease: true
-
- - uses: dotnet/nbgv@master
- with:
- setAllVars: true
-
- - name: Restore dependencies
- run: dotnet restore Build.csproj
-
- - name: Purge
- run: del src/Dapper.*/bin/Release/Dapper.*.nupkg
-
- - name: Build
- run: dotnet build Build.csproj --no-restore -c Release
-
- - name: Test
- run: dotnet test Build.csproj --no-build --verbosity normal -c Release -f net6.0 --filter FullyQualifiedName!~Integration
-
- - name: Pack
- if: ${{ success() && !github.base_ref }}
- run: dotnet pack src/Dapper.AOT/Dapper.AOT.csproj --no-build --verbosity normal -c Release
-
- - name: Push to MyGet
- if: ${{ success() && !github.base_ref }}
- run: dotnet nuget push src/Dapper.*/bin/Release/Dapper.*.nupkg --source https://www.myget.org/F/dapper/api/v2/package --api-key "$env:MYGETAPIKEY"
- env:
- MYGETAPIKEY: ${{ secrets.MYGETAPIKEY }}
+ - uses: actions/checkout@v2
+ with:
+ fetch-depth: 0 # depth is needed for nbgv
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: |
+ 6.0.x
+ 8.0.x
+ include-prerelease: true
+
+ - uses: dotnet/nbgv@master
+ with:
+ setAllVars: true
+
+ - name: Restore dependencies
+ run: dotnet restore Build.csproj
+
+ - name: Purge
+ run: del src/Dapper.*/bin/Release/Dapper.*.nupkg
+
+ - name: Build
+ run: dotnet build Build.csproj --no-restore -c Release
+
+ - name: Test
+ run: dotnet test Build.csproj --no-build --verbosity normal -c Release -f net6.0 --filter FullyQualifiedName!~Integration
+
+ - name: Pack
+ if: ${{ success() && !github.base_ref }}
+ run: dotnet pack src/Dapper.AOT/Dapper.AOT.csproj --no-build --verbosity normal -c Release
+
+ - name: Push to MyGet
+ if: ${{ success() && !github.base_ref }}
+ run: dotnet nuget push src/Dapper.*/bin/Release/Dapper.*.nupkg --source https://www.myget.org/F/dapper/api/v2/package --api-key "$env:MYGETAPIKEY"
+ env:
+ MYGETAPIKEY: ${{ secrets.MYGETAPIKEY }}
\ No newline at end of file
diff --git a/Dapper.AOT.sln b/Dapper.AOT.sln
index 15a5e283..0942f68d 100644
--- a/Dapper.AOT.sln
+++ b/Dapper.AOT.sln
@@ -14,6 +14,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Directory.Build.props = Directory.Build.props
Directory.Packages.props = Directory.Packages.props
.github\workflows\dotnet.yml = .github\workflows\dotnet.yml
+ .github\workflows\dotnet-integration.yml = .github\workflows\dotnet-integration.yml
global.json = global.json
nuget.config = nuget.config
README.md = README.md
@@ -40,6 +41,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "docs", "docs\docs.csproj",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UsageVanilla", "test\UsageVanilla\UsageVanilla.csproj", "{840EA1CA-62FF-409E-89F5-CD3BB269BAE3}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapper.AOT.Test.Integration.Executables", "test\Dapper.AOT.Test.Integration.Executables\Dapper.AOT.Test.Integration.Executables.csproj", "{9FA6A3B4-5722-40B2-8B58-F418F5D8C9A8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapper.AOT.Test.Integration", "test\Dapper.AOT.Test.Integration\Dapper.AOT.Test.Integration.csproj", "{CAB51448-4F0D-497B-A035-2B7ACB2DD9EB}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -78,6 +83,14 @@ Global
{840EA1CA-62FF-409E-89F5-CD3BB269BAE3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{840EA1CA-62FF-409E-89F5-CD3BB269BAE3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{840EA1CA-62FF-409E-89F5-CD3BB269BAE3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9FA6A3B4-5722-40B2-8B58-F418F5D8C9A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9FA6A3B4-5722-40B2-8B58-F418F5D8C9A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9FA6A3B4-5722-40B2-8B58-F418F5D8C9A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9FA6A3B4-5722-40B2-8B58-F418F5D8C9A8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CAB51448-4F0D-497B-A035-2B7ACB2DD9EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CAB51448-4F0D-497B-A035-2B7ACB2DD9EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CAB51448-4F0D-497B-A035-2B7ACB2DD9EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CAB51448-4F0D-497B-A035-2B7ACB2DD9EB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -91,6 +104,8 @@ Global
{A77B633C-573E-43CD-85A4-8063B33143B4} = {FE215D4B-811B-47BB-9F05-6382DD1C6729}
{C6527566-38F4-43CC-9E0E-91C4B8854774} = {1135D4FD-770E-41DF-920B-A8F75E42A832}
{840EA1CA-62FF-409E-89F5-CD3BB269BAE3} = {9A846B95-90CE-4335-9043-48C5B8EA4FB8}
+ {9FA6A3B4-5722-40B2-8B58-F418F5D8C9A8} = {9A846B95-90CE-4335-9043-48C5B8EA4FB8}
+ {CAB51448-4F0D-497B-A035-2B7ACB2DD9EB} = {9A846B95-90CE-4335-9043-48C5B8EA4FB8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A89CDAFA-494F-4168-9648-1138BA738D43}
diff --git a/Directory.Packages.props b/Directory.Packages.props
index e7f3774c..37851bc2 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -13,6 +13,7 @@
+
diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs
index 702feb08..951a1060 100644
--- a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs
+++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs
@@ -16,7 +16,6 @@
using System.Diagnostics;
using System.Globalization;
using System.Linq;
-using System.Runtime.InteropServices.ComTypes;
using System.Text;
using System.Threading;
using static Dapper.Internal.Inspection;
@@ -26,12 +25,33 @@ namespace Dapper.CodeAnalysis;
[Generator(LanguageNames.CSharp), DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed partial class DapperInterceptorGenerator : InterceptorGeneratorBase
{
+ private readonly bool _withInterceptionRecording = false;
+
public override ImmutableArray SupportedDiagnostics => DiagnosticsBase.All();
#pragma warning disable CS0067 // unused; retaining for now
public event Action? Log;
#pragma warning restore CS0067
+ ///
+ /// Creates an interceptor generator for Dapper
+ ///
+ public DapperInterceptorGenerator()
+ {
+ }
+
+ ///
+ /// Creates an interceptor generator for Dapper used for Tests.
+ ///
+ ///
+ /// It will insert very specific call with known method name.
+ /// Users will not have a reference to inserted assembly code, therefore: don't make it public
+ ///
+ internal DapperInterceptorGenerator(bool withInterceptionRecording)
+ {
+ _withInterceptionRecording = withInterceptionRecording;
+ }
+
public override void Initialize(IncrementalGeneratorInitializationContext context)
{
var nodes = context.SyntaxProvider.CreateSyntaxProvider(PreFilter, Parse)
@@ -360,6 +380,13 @@ internal void Generate(in GenerateState ctx)
sb.NewLine();
+ if (_withInterceptionRecording)
+ {
+ sb.Append("// record interception for tests assertions").NewLine();
+ sb.Append("global::Dapper.AOT.Test.Integration.Executables.Recording.InterceptorRecorderResolver.Resolve().Record();").NewLine();
+ sb.NewLine();
+ }
+
if (flags.HasAny(OperationFlags.GetRowParser))
{
WriteGetRowParser(sb, resultType, readers);
diff --git a/src/Dapper.AOT.Analyzers/Dapper.AOT.Analyzers.csproj b/src/Dapper.AOT.Analyzers/Dapper.AOT.Analyzers.csproj
index 47f8010a..0a113eb8 100644
--- a/src/Dapper.AOT.Analyzers/Dapper.AOT.Analyzers.csproj
+++ b/src/Dapper.AOT.Analyzers/Dapper.AOT.Analyzers.csproj
@@ -59,4 +59,8 @@
DapperAnalyzer.cs
+
+
+
+
diff --git a/src/Dapper.AOT.Analyzers/Internal/Inspection.cs b/src/Dapper.AOT.Analyzers/Internal/Inspection.cs
index 00c4d359..4e172c87 100644
--- a/src/Dapper.AOT.Analyzers/Internal/Inspection.cs
+++ b/src/Dapper.AOT.Analyzers/Internal/Inspection.cs
@@ -505,7 +505,7 @@ public DapperSpecialType DapperSpecialType
ContainingNamespace:
{
Name: "Dapper",
- IsGlobalNamespace: true
+ ContainingNamespace.IsGlobalNamespace: true
}
}) return DapperSpecialType.DbString;
diff --git a/test/Dapper.AOT.Test.Integration.Executables/Dapper.AOT.Test.Integration.Executables.csproj b/test/Dapper.AOT.Test.Integration.Executables/Dapper.AOT.Test.Integration.Executables.csproj
new file mode 100644
index 00000000..828f4929
--- /dev/null
+++ b/test/Dapper.AOT.Test.Integration.Executables/Dapper.AOT.Test.Integration.Executables.csproj
@@ -0,0 +1,16 @@
+
+
+ net8.0;net6.0;net48
+ Dapper.AOT.Test.Integration.Executables
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Dapper.AOT.Test.Integration.Executables/ExceptedCodeInterceptorRecorder.cs b/test/Dapper.AOT.Test.Integration.Executables/ExceptedCodeInterceptorRecorder.cs
new file mode 100644
index 00000000..76f6c489
--- /dev/null
+++ b/test/Dapper.AOT.Test.Integration.Executables/ExceptedCodeInterceptorRecorder.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using Dapper.AOT.Test.Integration.Executables.Recording;
+using Dapper.CodeAnalysis;
+
+namespace Dapper.AOT.Test.Integration.Executables;
+
+public class ExceptedCodeInterceptorRecorder : IInterceptorRecorder
+{
+ readonly string expectedFileName;
+
+ public ExceptedCodeInterceptorRecorder(string expectedFileName)
+ {
+ this.expectedFileName = expectedFileName ?? throw new ArgumentNullException(nameof(expectedFileName));
+ }
+
+ public bool WasCalled { get; private set; }
+ public string? Diagnostics { get; private set; }
+
+ public void Record()
+ {
+ WasCalled = true;
+ var diagnostics = new StringBuilder();
+
+ var stackTrace = new StackTrace(fNeedFileInfo: true);
+ var recentFrames = stackTrace.GetFrames().Take(15).ToList(); // we dont need everything
+
+ var userCodeFrameIndex = recentFrames.FindIndex(frame =>
+ frame.GetFileName()?.Contains(expectedFileName) == true && frame.GetMethod()?.Name.Equals(nameof(IExecutable.Execute)) == true);
+ if (userCodeFrameIndex == -1)
+ {
+ diagnostics.AppendLine("- User code execution is not found");
+ }
+
+ var dapperInterceptionFrameIndex = recentFrames.FindIndex(frame =>
+ frame.GetFileName()?.Contains(".generated.cs") == true && frame.GetFileName()?.Contains(nameof(DapperInterceptorGenerator)) == true);
+ if (dapperInterceptionFrameIndex == -1)
+ {
+ diagnostics.AppendLine("- User code execution is not found");
+ }
+
+ if (userCodeFrameIndex < dapperInterceptionFrameIndex)
+ {
+ diagnostics.AppendLine("- User code call should be higher (executed before) on the stack trace than intercepted code");
+ }
+
+ Diagnostics = diagnostics.ToString();
+ }
+}
\ No newline at end of file
diff --git a/test/Dapper.AOT.Test.Integration.Executables/IExecutable.cs b/test/Dapper.AOT.Test.Integration.Executables/IExecutable.cs
new file mode 100644
index 00000000..98a062d4
--- /dev/null
+++ b/test/Dapper.AOT.Test.Integration.Executables/IExecutable.cs
@@ -0,0 +1,8 @@
+using System.Data;
+
+namespace Dapper.AOT.Test.Integration.Executables;
+
+public interface IExecutable
+{
+ public T Execute(IDbConnection connection);
+}
\ No newline at end of file
diff --git a/test/Dapper.AOT.Test.Integration.Executables/Models/DbStringPoco.cs b/test/Dapper.AOT.Test.Integration.Executables/Models/DbStringPoco.cs
new file mode 100644
index 00000000..ed41851c
--- /dev/null
+++ b/test/Dapper.AOT.Test.Integration.Executables/Models/DbStringPoco.cs
@@ -0,0 +1,9 @@
+namespace Dapper.AOT.Test.Integration.Executables.Models;
+
+public class DbStringPoco
+{
+ public const string TableName = "dbStringPoco";
+
+ public int Id { get; set; }
+ public string Name { get; set; }
+}
\ No newline at end of file
diff --git a/test/Dapper.AOT.Test.Integration.Executables/Recording/IInterceptorRecorder.cs b/test/Dapper.AOT.Test.Integration.Executables/Recording/IInterceptorRecorder.cs
new file mode 100644
index 00000000..02ef13df
--- /dev/null
+++ b/test/Dapper.AOT.Test.Integration.Executables/Recording/IInterceptorRecorder.cs
@@ -0,0 +1,19 @@
+namespace Dapper.AOT.Test.Integration.Executables.Recording;
+
+public interface IInterceptorRecorder
+{
+ ///
+ /// returns true if expected interception was called
+ ///
+ public bool WasCalled { get; }
+
+ ///
+ /// Returns diagnostics of recording
+ ///
+ public string? Diagnostics { get; }
+
+ ///
+ /// Is executed in the interception
+ ///
+ public void Record();
+}
\ No newline at end of file
diff --git a/test/Dapper.AOT.Test.Integration.Executables/Recording/InterceptorRecorderResolver.cs b/test/Dapper.AOT.Test.Integration.Executables/Recording/InterceptorRecorderResolver.cs
new file mode 100644
index 00000000..c6119701
--- /dev/null
+++ b/test/Dapper.AOT.Test.Integration.Executables/Recording/InterceptorRecorderResolver.cs
@@ -0,0 +1,9 @@
+namespace Dapper.AOT.Test.Integration.Executables.Recording;
+
+public static class InterceptorRecorderResolver
+{
+ static IInterceptorRecorder? _recorder;
+
+ public static IInterceptorRecorder? Resolve() => _recorder;
+ public static void Register(IInterceptorRecorder? recorder) => _recorder = recorder;
+}
\ No newline at end of file
diff --git a/test/Dapper.AOT.Test.Integration.Executables/UserCode/DbStringUsage.cs b/test/Dapper.AOT.Test.Integration.Executables/UserCode/DbStringUsage.cs
new file mode 100644
index 00000000..a3181881
--- /dev/null
+++ b/test/Dapper.AOT.Test.Integration.Executables/UserCode/DbStringUsage.cs
@@ -0,0 +1,23 @@
+using System.Data;
+using System.Linq;
+using Dapper.AOT.Test.Integration.Executables.Models;
+
+namespace Dapper.AOT.Test.Integration.Executables.UserCode;
+
+[DapperAot]
+public class DbStringUsage : IExecutable
+{
+ public DbStringPoco Execute(IDbConnection connection)
+ {
+ var results = connection.Query(
+ $"select * from {DbStringPoco.TableName} where name = @name",
+ new
+ {
+ name = new DbString
+ {
+ Value = "my-dbString"
+ }
+ });
+ return results.First();
+ }
+}
\ No newline at end of file
diff --git a/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj b/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj
new file mode 100644
index 00000000..e69aaed0
--- /dev/null
+++ b/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj
@@ -0,0 +1,31 @@
+
+
+
+ net8.0
+ Dapper.AOT.Test.Integration
+ enable
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Dapper.AOT.Test.Integration/DbStringTests.cs b/test/Dapper.AOT.Test.Integration/DbStringTests.cs
new file mode 100644
index 00000000..9822803e
--- /dev/null
+++ b/test/Dapper.AOT.Test.Integration/DbStringTests.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Data;
+using Dapper.AOT.Test.Integration.Executables.Models;
+using Dapper.AOT.Test.Integration.Executables.UserCode;
+using Dapper.AOT.Test.Integration.Setup;
+
+namespace Dapper.AOT.Test.Integration;
+
+[Collection(SharedPostgresqlClient.Collection)]
+public class DbStringTests : IntegrationTestsBase
+{
+ public DbStringTests(PostgresqlFixture fixture) : base(fixture)
+ {
+ }
+
+ protected override void SetupDatabase(IDbConnection dbConnection)
+ {
+ base.SetupDatabase(dbConnection);
+
+ dbConnection.Execute($"""
+ CREATE TABLE IF NOT EXISTS {DbStringPoco.TableName}(
+ id integer PRIMARY KEY,
+ name varchar(40)
+ );
+
+ TRUNCATE {DbStringPoco.TableName};
+
+ INSERT INTO {DbStringPoco.TableName} (id, name)
+ VALUES (1, 'my-dbString'),
+ (2, 'my-poco'),
+ (3, 'your-data');
+ """);
+ }
+
+ [Fact]
+ public void DbString_BasicUsage_InterceptsAndReturnsExpectedData()
+ {
+ var result = ExecuteInterceptedUserCode(DbConnection);
+
+ Assert.True(result.Id.Equals(1));
+ Assert.True(result.Name.Equals("my-dbString", StringComparison.InvariantCultureIgnoreCase));
+ }
+}
\ No newline at end of file
diff --git a/test/Dapper.AOT.Test.Integration/Helpers/CompilationExtensions.cs b/test/Dapper.AOT.Test.Integration/Helpers/CompilationExtensions.cs
new file mode 100644
index 00000000..d004eaf0
--- /dev/null
+++ b/test/Dapper.AOT.Test.Integration/Helpers/CompilationExtensions.cs
@@ -0,0 +1,46 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using Dapper.AOT.Test.Integration.Setup;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Emit;
+
+namespace Dapper.AOT.Test.Integration.Helpers;
+
+internal static class CompilationExtensions
+{
+ public static Assembly CompileToAssembly(this Compilation compilation)
+ {
+ using var peStream = new MemoryStream();
+ using var pdbstream = new MemoryStream();
+ var dbg = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? DebugInformationFormat.Pdb : DebugInformationFormat.PortablePdb;
+
+ var emitResult = compilation.Emit(peStream, pdbstream, null, null, null, new EmitOptions(false, dbg));
+ if (!emitResult.Success)
+ {
+ TryThrowErrors(emitResult.Diagnostics);
+ }
+
+ peStream.Position = pdbstream.Position = 0;
+ return Assembly.Load(peStream.ToArray(), pdbstream.ToArray());
+ }
+
+ static void TryThrowErrors(IEnumerable items)
+ {
+ var errors = new List();
+ foreach (var item in items)
+ {
+ if (item.Severity == DiagnosticSeverity.Error)
+ {
+ errors.Add(item.GetMessage(CultureInfo.InvariantCulture));
+ }
+ }
+
+ if (errors.Count > 0)
+ {
+ throw new ExpressionParsingException(errors);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Dapper.AOT.Test.Integration/Setup/ExpressionParsingException.cs b/test/Dapper.AOT.Test.Integration/Setup/ExpressionParsingException.cs
new file mode 100644
index 00000000..46ef7fc2
--- /dev/null
+++ b/test/Dapper.AOT.Test.Integration/Setup/ExpressionParsingException.cs
@@ -0,0 +1,9 @@
+using System;
+using System.Collections.Generic;
+
+namespace Dapper.AOT.Test.Integration.Setup;
+
+class ExpressionParsingException(IEnumerable errors)
+ : Exception(string.Join(Environment.NewLine, errors))
+{
+}
\ No newline at end of file
diff --git a/test/Dapper.AOT.Test.Integration/Setup/IntegrationTestsBase.cs b/test/Dapper.AOT.Test.Integration/Setup/IntegrationTestsBase.cs
new file mode 100644
index 00000000..762259de
--- /dev/null
+++ b/test/Dapper.AOT.Test.Integration/Setup/IntegrationTestsBase.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using Dapper.AOT.Test.Integration.Executables;
+using Dapper.AOT.Test.Integration.Executables.Recording;
+using Dapper.AOT.Test.Integration.Helpers;
+using Dapper.CodeAnalysis;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Dapper.AOT.Test.Integration.Setup;
+
+public abstract class IntegrationTestsBase
+{
+ private const string UserCodeFileName = "UserCode.cs";
+ static readonly CSharpParseOptions InterceptorSupportedParseOptions = new CSharpParseOptions(LanguageVersion.Preview)
+ .WithFeatures(new[]
+ {
+ new KeyValuePair("InterceptorsPreviewNamespaces", "$(InterceptorsPreviewNamespaces);Dapper.AOT"),
+ });
+
+ protected readonly IDbConnection DbConnection;
+
+ protected IntegrationTestsBase(PostgresqlFixture fixture)
+ {
+ DbConnection = fixture.NpgsqlConnection;
+ SetupDatabase(DbConnection);
+ }
+
+ protected virtual void SetupDatabase(IDbConnection dbConnection)
+ {
+ }
+
+ protected TResult ExecuteInterceptedUserCode(IDbConnection dbConnection)
+ {
+ var userSourceCode = ReadUserSourceCode();
+
+ var inputCompilation = CSharpCompilation.Create(
+ assemblyName: "Test.dll",
+ syntaxTrees: [ Parse(UserCodeFileName, userSourceCode) ],
+ references: [
+ // dotnet System.Runtime
+ MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
+ MetadataReference.CreateFromFile(Assembly.Load("System.Runtime").Location),
+
+ // Dapper and Dapper.AOT
+ MetadataReference.CreateFromFile(typeof(Dapper.SqlMapper).Assembly.Location), // Dapper
+ MetadataReference.CreateFromFile(typeof(Dapper.CommandFactory).Assembly.Location), // Dapper.AOT
+
+ // user code
+ MetadataReference.CreateFromFile(typeof(Dapper.AOT.Test.Integration.Executables.UserCode.DbStringUsage).Assembly.Location), // Dapper.AOT.Test.Integration.Executables
+
+ // dependencies
+ MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(System.Data.Common.DbConnection).Assembly.Location), // System.Data.Common
+
+ // Additional stuff required by Dapper.AOT generators
+ MetadataReference.CreateFromFile(Assembly.Load("System.Collections").Location), // System.Collections
+ ],
+ options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
+
+ var interceptorRecorder = new ExceptedCodeInterceptorRecorder(UserCodeFileName);
+ InterceptorRecorderResolver.Register(interceptorRecorder);
+
+ var generator = new DapperInterceptorGenerator(withInterceptionRecording: true);
+ GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { generator.AsSourceGenerator() }, parseOptions: InterceptorSupportedParseOptions);
+ _ = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics);
+
+ var assembly = outputCompilation.CompileToAssembly();
+ var type = assembly.GetTypes().Single(t => t.FullName == typeof(TExecutable).FullName);
+ var executableInstance = Activator.CreateInstance(type);
+ var mainMethod = type.GetMethod(nameof(IExecutable.Execute), BindingFlags.Public | BindingFlags.Instance);
+ var result = mainMethod!.Invoke(obj: executableInstance, [ dbConnection ]);
+
+ Assert.True(interceptorRecorder.WasCalled);
+ Assert.True(string.IsNullOrEmpty(interceptorRecorder.Diagnostics), userMessage: interceptorRecorder.Diagnostics);
+
+ return (TResult)result!;
+ }
+
+ private static SyntaxTree Parse(string filename, string text)
+ {
+ var stringText = SourceText.From(text, Encoding.UTF8);
+ return SyntaxFactory.ParseSyntaxTree(stringText, InterceptorSupportedParseOptions, filename);
+ }
+
+ private static string ReadUserSourceCode()
+ {
+ var userTypeName = typeof(TExecutable).Name;
+
+ // it's very fragile to get user code cs files into output directory (btw we can't remove them from compilation, because we will use them for assertions)
+ // so let's simply get back to test\ dir, and try to find Executables.UserCode from there
+ var testDir = Directory.GetParent(Directory.GetParent(Directory.GetParent(Directory.GetParent(Directory.GetCurrentDirectory())!.FullName)!.FullName)!.FullName);
+ return File.ReadAllText(Path.Combine(testDir!.FullName, "Dapper.AOT.Test.Integration.Executables", "UserCode", $"{userTypeName}.cs"));
+ }
+}
\ No newline at end of file
diff --git a/test/Dapper.AOT.Test.Integration/Setup/PostgresqlFixture.cs b/test/Dapper.AOT.Test.Integration/Setup/PostgresqlFixture.cs
new file mode 100644
index 00000000..0d85fff2
--- /dev/null
+++ b/test/Dapper.AOT.Test.Integration/Setup/PostgresqlFixture.cs
@@ -0,0 +1,51 @@
+using Npgsql;
+using System.Threading.Tasks;
+using Testcontainers.PostgreSql;
+
+namespace Dapper.AOT.Test.Integration.Setup;
+
+[CollectionDefinition(Collection)]
+public class SharedPostgresqlClient : ICollectionFixture
+{
+ public const string Collection = nameof(SharedPostgresqlClient);
+}
+
+public sealed class PostgresqlFixture : IAsyncLifetime
+{
+ private readonly PostgreSqlContainer _postgresContainer = new PostgreSqlBuilder()
+ .WithImage("postgres:15-alpine")
+ .Build();
+
+ public string ConnectionString { get; private set; } = null!;
+
+ private NpgsqlConnection? _sharedConnection;
+
+ public NpgsqlConnection NpgsqlConnection
+ => _sharedConnection ??= CreateOpenConnection();
+
+ public NpgsqlConnection CreateOpenConnection()
+ {
+ var conn = new NpgsqlConnection(ConnectionString);
+ conn.Open();
+ return conn;
+ }
+
+ async Task IAsyncLifetime.InitializeAsync()
+ {
+ await _postgresContainer.StartAsync();
+ ConnectionString = _postgresContainer.GetConnectionString();
+ }
+
+ async Task IAsyncLifetime.DisposeAsync()
+ {
+ await using (_postgresContainer)
+ {
+ var tmp = _sharedConnection;
+ _sharedConnection = null;
+ if (tmp is not null)
+ {
+ await tmp.DisposeAsync();
+ }
+ }
+ }
+}
diff --git a/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj b/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj
index 67a758e5..663a4053 100644
--- a/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj
+++ b/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj
@@ -33,7 +33,7 @@
-
+
@@ -48,8 +48,6 @@
runtime; build; native; contentfiles; analyzers
-
-
@@ -59,4 +57,9 @@
+
+
+
+
+
diff --git a/test/Dapper.AOT.Test/Integration/PostgresqlFixture.cs b/test/Dapper.AOT.Test/Integration/PostgresqlFixture.cs
index 59329192..b2b5e506 100644
--- a/test/Dapper.AOT.Test/Integration/PostgresqlFixture.cs
+++ b/test/Dapper.AOT.Test/Integration/PostgresqlFixture.cs
@@ -1,5 +1,5 @@
-using Npgsql;
-using System.Threading.Tasks;
+using System.Threading.Tasks;
+using Npgsql;
using Testcontainers.PostgreSql;
using Xunit;