1
+ // Copyright (c) .NET Foundation and contributors. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ using System ;
5
+ using System . IO ;
6
+ using System . Reflection ;
7
+ using FluentAssertions ;
8
+ using System . Threading . Tasks ;
9
+ using Microsoft . CodeAnalysis ;
10
+ using Microsoft . CodeAnalysis . CSharp ;
11
+ using Microsoft . DotNet . Interactive . Commands ;
12
+ using Microsoft . DotNet . Interactive . Events ;
13
+ using Microsoft . DotNet . Interactive . Tests . Utility ;
14
+ using Xunit ;
15
+ using Xunit . Abstractions ;
16
+
17
+ namespace Microsoft . DotNet . Interactive . Tests
18
+ {
19
+ #pragma warning disable 8509
20
+ public class LanguageKernelAssemblyReferenceTests : LanguageKernelTestBase
21
+ {
22
+ public LanguageKernelAssemblyReferenceTests ( ITestOutputHelper output ) : base ( output )
23
+ {
24
+ }
25
+
26
+ [ Theory ]
27
+ [ InlineData ( Language . CSharp ) ]
28
+ [ InlineData ( Language . FSharp ) ]
29
+ public async Task it_can_load_assembly_references_using_r_directive_single_submission ( Language language )
30
+ {
31
+ var kernel = CreateKernel ( language ) ;
32
+
33
+ // F# strings treat \ as an escape character. So do C# strings, except #r in C# is special, and doesn't. F# usually uses @ strings for paths @"c:\temp\...."
34
+ var dllPath = CreateDllInCurrentDirectory ( ) ;
35
+
36
+ var source = language switch
37
+ {
38
+ Language . FSharp => $@ "#r @""{ dllPath . FullName } ""
39
+ typeof<Hello>.Name" ,
40
+
41
+ Language . CSharp => $@ "#r ""{ dllPath . FullName } ""
42
+ typeof(Hello).Name"
43
+ } ;
44
+
45
+ await SubmitCode ( kernel , source ) ;
46
+
47
+ KernelEvents . Should ( ) . NotContainErrors ( ) ;
48
+
49
+ KernelEvents
50
+ . Should ( )
51
+ . ContainSingle < ReturnValueProduced > ( )
52
+ . Which
53
+ . Value
54
+ . Should ( )
55
+ . Be ( "Hello" ) ;
56
+ }
57
+
58
+ [ Theory ]
59
+ [ InlineData ( Language . CSharp ) ]
60
+ [ InlineData ( Language . FSharp ) ]
61
+ public async Task it_can_load_assembly_references_using_r_directive_separate_submissions ( Language language )
62
+ {
63
+ var kernel = CreateKernel ( language ) ;
64
+
65
+ // F# strings treat \ as an escape character. So do C# strings, except #r in C# is special, and doesn't. F# usually uses @ strings for paths @"c:\temp\...."
66
+ var dllPath = CreateDllInCurrentDirectory ( ) ;
67
+
68
+ var source = language switch
69
+ {
70
+ Language . FSharp => new [ ]
71
+ {
72
+ $ "#r @\" { dllPath . FullName } \" ",
73
+ "typeof<Hello>.Name"
74
+ } ,
75
+
76
+ Language . CSharp => new [ ]
77
+ {
78
+ $ "#r \" { dllPath . FullName } \" ",
79
+ "typeof(Hello).Name"
80
+ }
81
+ } ;
82
+
83
+ await SubmitCode ( kernel , source ) ;
84
+
85
+ KernelEvents . Should ( ) . NotContainErrors ( ) ;
86
+
87
+ KernelEvents
88
+ . Should ( )
89
+ . ContainSingle < ReturnValueProduced > ( )
90
+ . Which
91
+ . Value
92
+ . Should ( )
93
+ . Be ( "Hello" ) ;
94
+ }
95
+
96
+ [ Theory ]
97
+ [ InlineData ( Language . CSharp ) ]
98
+ [ InlineData ( Language . FSharp ) ]
99
+ public async Task it_can_load_assembly_references_using_r_directive_with_relative_path ( Language language )
100
+ {
101
+ var kernel = CreateKernel ( language ) ;
102
+
103
+ var dllName = CreateDllInCurrentDirectory ( ) ;
104
+
105
+ var code = language switch
106
+ {
107
+ Language . CSharp => $ "#r \" { dllName . Name } \" ",
108
+ Language . FSharp => $ "#r \" { dllName . Name } \" "
109
+ } ;
110
+
111
+ var command = new SubmitCode ( code ) ;
112
+
113
+ await kernel . SendAsync ( command ) ;
114
+
115
+ KernelEvents . Should ( ) . NotContainErrors ( ) ;
116
+
117
+ KernelEvents . Should ( )
118
+ . ContainSingle < CommandSucceeded > ( c => c . Command == command ) ;
119
+ }
120
+
121
+ [ Theory ]
122
+ [ InlineData ( Language . CSharp ) ]
123
+ // [InlineData(Language.FSharp)] Not supported in F#
124
+ public async Task it_can_load_assembly_references_using_r_directive_with_relative_path_after_user_code_changes_current_directory ( Language language )
125
+ {
126
+ var currentDirectory = Directory . GetCurrentDirectory ( ) ;
127
+ DisposeAfterTest ( ( ) => Directory . SetCurrentDirectory ( currentDirectory ) ) ;
128
+
129
+ var kernel = CreateKernel ( language ) ;
130
+
131
+ //Even when a user changes the current directory, loading from a relative path is not affected.
132
+ await kernel . SendAsync ( new SubmitCode ( "System.IO.Directory.SetCurrentDirectory(\" ..\" )" ) ) ;
133
+
134
+ var dllName = CreateDllInCurrentDirectory ( ) ;
135
+
136
+ var code = language switch
137
+ {
138
+ Language . CSharp => $ "#r \" { dllName . Name } \" \n new Hello()",
139
+ Language . FSharp => $ "#r \" { dllName . Name } \" \n new Hello()"
140
+ } ;
141
+
142
+ var command = new SubmitCode ( code ) ;
143
+
144
+ await kernel . SendAsync ( command ) ;
145
+
146
+ KernelEvents . Should ( ) . NotContainErrors ( ) ;
147
+
148
+ KernelEvents . Should ( )
149
+ . ContainSingle < CommandSucceeded > ( c => c . Command == command ) ;
150
+ }
151
+
152
+ private FileInfo CreateDllInCurrentDirectory ( )
153
+ {
154
+ var assemblyName = Guid . NewGuid ( ) . ToString ( "N" ) ;
155
+ var dllName = assemblyName + ".dll" ;
156
+
157
+ var systemRefLocation = typeof ( object ) . GetTypeInfo ( ) . Assembly . Location ;
158
+ var systemReference = MetadataReference . CreateFromFile ( systemRefLocation ) ;
159
+
160
+ CSharpCompilation . Create ( assemblyName )
161
+ . WithOptions ( new CSharpCompilationOptions ( OutputKind . DynamicallyLinkedLibrary ) )
162
+ . AddReferences ( systemReference )
163
+ . AddSyntaxTrees ( CSharpSyntaxTree . ParseText ( "public class Hello { }" ) )
164
+ . Emit ( dllName ) ;
165
+
166
+ return new FileInfo ( Path . Combine ( Directory . GetCurrentDirectory ( ) , dllName ) ) ;
167
+ }
168
+ }
169
+ }
0 commit comments