66using System . IO ;
77using System . Linq ;
88using System . Reflection ;
9+ using System . Runtime . CompilerServices ;
910using System . Runtime . InteropServices ;
1011using System . Text . Json ;
1112using System . Threading . Tasks ;
@@ -201,11 +202,11 @@ public async Task L2_StagesOneAndTwo_AreGreen_WithZeroArtifacts()
201202 }
202203
203204 /// <summary>
204- /// MSBuild -generated friend-assembly metadata must become the equivalent
205- /// G# assembly annotation so migrated test projects can access internals .
205+ /// SDK -generated and hand-authored friend-assembly declarations must each
206+ /// reach the compiled G# assembly exactly once .
206207 /// </summary>
207208 [ Fact ]
208- public async Task L2_Translate_PreservesInternalsVisibleTo ( )
209+ public async Task L2_Compile_EmitsInternalsVisibleToExactlyOnce ( )
209210 {
210211 string compiler = FindCompiler ( ) ;
211212 if ( compiler is null )
@@ -214,22 +215,86 @@ public async Task L2_Translate_PreservesInternalsVisibleTo()
214215 }
215216
216217 string corpus = ResolveCorpusDir ( ) ;
218+ string repoRoot = GsharpTestProjectRunner . FindRepoRoot ( ) ;
219+ ( string NupkgPath , string Version ) ? sdk =
220+ GsharpTestProjectRunner . ResolveLocalSdkPackage ( repoRoot ) ;
221+ if ( sdk is null )
222+ {
223+ return ;
224+ }
225+
226+ CorpusApp originalL2 = CorpusDiscovery . FindById ( corpus , "corpus/L2-Library" ) ;
227+ string sourceRoot = NewOutputRoot ( "l2-friend-source" ) ;
228+ string sourceProjectDir = Path . Combine ( sourceRoot , "L2-Library" ) ;
229+ Directory . CreateDirectory ( sourceProjectDir ) ;
230+ File . Copy (
231+ Path . Combine ( corpus , "Directory.Build.props" ) ,
232+ Path . Combine ( sourceRoot , "Directory.Build.props" ) ) ;
233+ string projectPath = Path . Combine ( sourceProjectDir , Path . GetFileName ( originalL2 . ProjectPath ) ) ;
234+ File . Copy ( originalL2 . ProjectPath , projectPath ) ;
235+ File . WriteAllText (
236+ projectPath ,
237+ File . ReadAllText ( projectPath ) . Replace (
238+ "<PropertyGroup>" ,
239+ "<PropertyGroup>" + Environment . NewLine + " <TargetFramework>net10.0</TargetFramework>" ,
240+ StringComparison . Ordinal ) ) ;
241+ File . WriteAllText ( Path . Combine ( sourceProjectDir , "FriendAssembly.cs" ) , """
242+ using System.Runtime.CompilerServices;
243+
244+ [assembly: InternalsVisibleTo("L2-Library.Source.Tests")]
245+
246+ public sealed class Marker
247+ {
248+ }
249+ """ ) ;
250+ var l2 = new CorpusApp (
251+ originalL2 . Id ,
252+ projectPath ,
253+ originalL2 . TargetKind ) ;
217254 string outRoot = NewOutputRoot ( "l2-friend-assembly" ) ;
218255 var options = new PipelineOptions { GscPath = compiler , OutputRoot = outRoot } ;
219256 var pipeline = new MigrationPipeline ( options , new IMigrationStage [ ] { new TranslateStage ( ) } ) ;
220257
221- CorpusApp l2 = CorpusDiscovery . FindById ( corpus , "corpus/L2-Library" ) ;
222258 RunResult result = await pipeline . RunAsync ( new [ ] { l2 } ) ;
223259 AppResult app = Assert . Single ( result . Apps ) ;
224260
225261 Assert . True ( app . Succeeded ) ;
226- string assemblyInfo = Directory . GetFiles (
227- Path . Combine ( outRoot , result . RunId , MigrationPipeline . SanitizeAppId ( l2 . Id ) ) ,
228- "AssemblyInfo.gs" ,
229- SearchOption . AllDirectories ) . Single ( ) ;
262+ string appDir = Path . Combine ( outRoot , result . RunId , MigrationPipeline . SanitizeAppId ( l2 . Id ) ) ;
263+ string generatedProjectPath = Path . Combine ( appDir , "L2-Library.gsproj" ) ;
264+ GSharpProjectTransformer . Transform (
265+ projectPath ,
266+ appDir ,
267+ "Gsharp.NET.Sdk/" + sdk . Value . Version ,
268+ generatedProjectPaths : null ) . Save (
269+ generatedProjectPath ,
270+ System . Xml . Linq . SaveOptions . DisableFormatting ) ;
271+ GsharpTestProjectRunner . EnsureInLocalFeed ( repoRoot , sdk . Value . NupkgPath ) ;
272+ GsharpTestProjectRunner . WriteIsolationBoundary ( appDir ) ;
273+ ProcessRunResult build = ProcessRunner . Run (
274+ "dotnet" ,
275+ new [ ]
276+ {
277+ "build" ,
278+ generatedProjectPath ,
279+ "-c" ,
280+ "Release" ,
281+ "-p:RestoreConfigFile=" + Path . Combine ( repoRoot , "nuget.config" ) ,
282+ } ,
283+ appDir ,
284+ TimeSpan . FromMinutes ( 5 ) ) ;
285+ Assert . True ( build . ExitCode == 0 , build . Output ) ;
286+
287+ string assemblyPath = Path . Combine ( appDir , "bin" , "Release" , "net10.0" , "L2-Library.dll" ) ;
288+ Assembly assembly = Assembly . LoadFile ( assemblyPath ) ;
289+ string [ ] friendAssemblies = assembly . GetCustomAttributesData ( )
290+ . Where ( attribute => attribute . AttributeType == typeof ( InternalsVisibleToAttribute ) )
291+ . Select ( attribute => ( string ) Assert . Single ( attribute . ConstructorArguments ) . Value )
292+ . OrderBy ( name => name , StringComparer . Ordinal )
293+ . ToArray ( ) ;
294+
230295 Assert . Equal (
231- "@assembly:InternalsVisibleTo( \" L2-Library.Tests\" )" + Environment . NewLine ,
232- File . ReadAllText ( assemblyInfo ) ) ;
296+ new [ ] { " L2-Library.Source. Tests" , "L2-Library.Tests" } ,
297+ friendAssemblies ) ;
233298 }
234299
235300 /// <summary>
0 commit comments