1
+ using System ;
2
+ using System . IO ;
3
+ using System . Linq ;
4
+ using System . Runtime . CompilerServices ;
5
+ using System . Text ;
6
+ using FluentAssertions ;
7
+ using Xunit ;
8
+ using Microsoft . NET . HostModel . AppHost ;
9
+ using Microsoft . DotNet . CoreSetup . Test ;
10
+
11
+ namespace Microsoft . NET . HostModel . Tests
12
+ {
13
+ public class AppHostUpdateTests
14
+ {
15
+ /// <summary>
16
+ /// hash value embedded in default apphost executable in a place where the path to the app binary should be stored.
17
+ /// </summary>
18
+ private const string AppBinaryPathPlaceholder = "c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2" ;
19
+ private readonly static byte [ ] AppBinaryPathPlaceholderSearchValue = Encoding . UTF8 . GetBytes ( AppBinaryPathPlaceholder ) ;
20
+
21
+ [ Fact ]
22
+ public void ItEmbedsAppBinaryPath ( )
23
+ {
24
+ using ( TestDirectory testDirectory = TestDirectory . Create ( ) )
25
+ {
26
+ string sourceAppHostMock = PrepareAppHostMockFile ( testDirectory ) ;
27
+ string destinationFilePath = Path . Combine ( testDirectory . Path , "DestinationAppHost.exe.mock" ) ;
28
+ string appBinaryFilePath = "Test/App/Binary/Path.dll" ;
29
+
30
+ HostWriter . CreateAppHost (
31
+ sourceAppHostMock ,
32
+ destinationFilePath ,
33
+ appBinaryFilePath ) ;
34
+
35
+ byte [ ] binaryPathBlob = Encoding . UTF8 . GetBytes ( appBinaryFilePath ) ;
36
+ byte [ ] result = File . ReadAllBytes ( destinationFilePath ) ;
37
+ result
38
+ . Skip ( WindowsFileHeader . Length )
39
+ . Take ( binaryPathBlob . Length )
40
+ . Should ( )
41
+ . BeEquivalentTo ( binaryPathBlob ) ;
42
+
43
+ BitConverter
44
+ . ToUInt16 ( result , SubsystemOffset )
45
+ . Should ( )
46
+ . Be ( 3 ) ;
47
+ }
48
+ }
49
+
50
+ [ Fact ]
51
+ public void ItFailsToEmbedAppBinaryIfHashIsWrong ( )
52
+ {
53
+ using ( TestDirectory testDirectory = TestDirectory . Create ( ) )
54
+ {
55
+ string sourceAppHostMock = PrepareAppHostMockFile ( testDirectory , content =>
56
+ {
57
+ // Corrupt the hash value
58
+ content [ WindowsFileHeader . Length + 1 ] ++ ;
59
+ } ) ;
60
+ string destinationFilePath = Path . Combine ( testDirectory . Path , "DestinationAppHost.exe.mock" ) ;
61
+ string appBinaryFilePath = "Test/App/Binary/Path.dll" ;
62
+
63
+ Assert . Throws < PlaceHolderNotFoundInAppHostException > ( ( ) =>
64
+ HostWriter . CreateAppHost (
65
+ sourceAppHostMock ,
66
+ destinationFilePath ,
67
+ appBinaryFilePath ) ) ;
68
+
69
+ File . Exists ( destinationFilePath ) . Should ( ) . BeFalse ( ) ;
70
+ }
71
+ }
72
+
73
+ [ Fact ]
74
+ public void ItFailsToEmbedTooLongAppBinaryPath ( )
75
+ {
76
+ using ( TestDirectory testDirectory = TestDirectory . Create ( ) )
77
+ {
78
+ string sourceAppHostMock = PrepareAppHostMockFile ( testDirectory ) ;
79
+ string destinationFilePath = Path . Combine ( testDirectory . Path , "DestinationAppHost.exe.mock" ) ;
80
+ string appBinaryFilePath = new string ( 'a' , 1024 + 5 ) ;
81
+
82
+ Assert . Throws < AppNameTooLongException > ( ( ) =>
83
+ HostWriter . CreateAppHost (
84
+ sourceAppHostMock ,
85
+ destinationFilePath ,
86
+ appBinaryFilePath ) ) ;
87
+
88
+ File . Exists ( destinationFilePath ) . Should ( ) . BeFalse ( ) ;
89
+ }
90
+ }
91
+
92
+ [ Fact ]
93
+ public void ItCanSetWindowsGUISubsystem ( )
94
+ {
95
+ using ( TestDirectory testDirectory = TestDirectory . Create ( ) )
96
+ {
97
+ string sourceAppHostMock = PrepareAppHostMockFile ( testDirectory ) ;
98
+ string destinationFilePath = Path . Combine ( testDirectory . Path , "DestinationAppHost.exe.mock" ) ;
99
+ string appBinaryFilePath = "Test/App/Binary/Path.dll" ;
100
+
101
+ HostWriter . CreateAppHost (
102
+ sourceAppHostMock ,
103
+ destinationFilePath ,
104
+ appBinaryFilePath ,
105
+ windowsGraphicalUserInterface : true ) ;
106
+
107
+ BitConverter
108
+ . ToUInt16 ( File . ReadAllBytes ( destinationFilePath ) , SubsystemOffset )
109
+ . Should ( )
110
+ . Be ( 2 ) ;
111
+ }
112
+ }
113
+
114
+ [ Fact ]
115
+ public void ItFailsToSetGUISubsystemOnNonWindowsPEFile ( )
116
+ {
117
+ using ( TestDirectory testDirectory = TestDirectory . Create ( ) )
118
+ {
119
+ string sourceAppHostMock = PrepareAppHostMockFile ( testDirectory , content =>
120
+ {
121
+ // Windows PE files must start with 0x5A4D, so write some other value here.
122
+ content [ 0 ] = 1 ;
123
+ content [ 1 ] = 2 ;
124
+ } ) ;
125
+ string destinationFilePath = Path . Combine ( testDirectory . Path , "DestinationAppHost.exe.mock" ) ;
126
+ string appBinaryFilePath = "Test/App/Binary/Path.dll" ;
127
+
128
+ Assert . Throws < AppHostNotPEFileException > ( ( ) =>
129
+ HostWriter . CreateAppHost (
130
+ sourceAppHostMock ,
131
+ destinationFilePath ,
132
+ appBinaryFilePath ,
133
+ windowsGraphicalUserInterface : true ) ) ;
134
+
135
+ File . Exists ( destinationFilePath ) . Should ( ) . BeFalse ( ) ;
136
+ }
137
+ }
138
+
139
+ [ Fact ]
140
+ public void ItFailsToSetGUISubsystemWithWrongDefault ( )
141
+ {
142
+ using ( TestDirectory testDirectory = TestDirectory . Create ( ) )
143
+ {
144
+ string sourceAppHostMock = PrepareAppHostMockFile ( testDirectory , content =>
145
+ {
146
+ // Corrupt the value of the subsystem (the default should be 3)
147
+ content [ SubsystemOffset ] = 42 ;
148
+ } ) ;
149
+ string destinationFilePath = Path . Combine ( testDirectory . Path , "DestinationAppHost.exe.mock" ) ;
150
+ string appBinaryFilePath = "Test/App/Binary/Path.dll" ;
151
+
152
+ Assert . Throws < AppHostNotCUIException > ( ( ) =>
153
+ HostWriter . CreateAppHost (
154
+ sourceAppHostMock ,
155
+ destinationFilePath ,
156
+ appBinaryFilePath ,
157
+ windowsGraphicalUserInterface : true ) ) ;
158
+
159
+ File . Exists ( destinationFilePath ) . Should ( ) . BeFalse ( ) ;
160
+ }
161
+ }
162
+
163
+ private string PrepareAppHostMockFile ( TestDirectory testDirectory , Action < byte [ ] > customize = null )
164
+ {
165
+ // For now we're testing the AppHost on Windows PE files only.
166
+ // The only customization which we do on non-Windows files is the embedding
167
+ // of the binary path, which works the same regardless of the file format.
168
+
169
+ int size = WindowsFileHeader . Length + AppBinaryPathPlaceholderSearchValue . Length ;
170
+ byte [ ] content = new byte [ size ] ;
171
+ Array . Copy ( WindowsFileHeader , 0 , content , 0 , WindowsFileHeader . Length ) ;
172
+ Array . Copy ( AppBinaryPathPlaceholderSearchValue , 0 , content , WindowsFileHeader . Length , AppBinaryPathPlaceholderSearchValue . Length ) ;
173
+
174
+ customize ? . Invoke ( content ) ;
175
+
176
+ string filePath = Path . Combine ( testDirectory . Path , "SourceAppHost.exe.mock" ) ;
177
+ File . WriteAllBytes ( filePath , content ) ;
178
+ return filePath ;
179
+ }
180
+
181
+ private const int SubsystemOffset = 0xF0 + 0x5C ;
182
+
183
+ // This is a dump of first 350 bytes of a windows apphost.exe
184
+ // This includes the PE header and part of the Optional header
185
+ private static readonly byte [ ] WindowsFileHeader = new byte [ ] {
186
+ 77 , 90 , 144 , 0 , 3 , 0 , 0 , 0 , 4 , 0 , 0 , 0 , 255 , 255 , 0 , 0 , 184 ,
187
+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 64 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
188
+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
189
+ 240 , 0 , 0 , 0 , 14 , 31 , 186 , 14 , 0 , 180 , 9 , 205 ,
190
+ 33 , 184 , 1 , 76 , 205 , 33 , 84 , 104 , 105 , 115 , 32 , 112 , 114 , 111 ,
191
+ 103 , 114 , 97 , 109 , 32 , 99 , 97 , 110 , 110 , 111 , 116 , 32 , 98 , 101 ,
192
+ 32 , 114 , 117 , 110 , 32 , 105 , 110 , 32 , 68 , 79 , 83 , 32 , 109 , 111 ,
193
+ 100 , 101 , 46 , 13 , 13 , 10 , 36 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 30 , 91 , 134 ,
194
+ 254 , 90 , 58 , 232 , 173 , 90 , 58 , 232 , 173 , 90 , 58 , 232 , 173 , 97 ,
195
+ 100 , 235 , 172 , 93 , 58 , 232 , 173 , 97 , 100 , 237 , 172 , 99 , 58 ,
196
+ 232 , 173 , 97 , 100 , 236 , 172 , 123 , 58 , 232 , 173 , 83 , 66 , 123 ,
197
+ 173 , 72 , 58 , 232 , 173 , 135 , 197 , 35 , 173 , 89 , 58 , 232 , 173 ,
198
+ 90 , 58 , 233 , 173 , 204 , 58 , 232 , 173 , 205 , 100 , 237 , 172 , 92 ,
199
+ 58 , 232 , 173 , 200 , 100 , 23 , 173 , 91 , 58 , 232 , 173 , 205 , 100 , 234 ,
200
+ 172 , 91 , 58 , 232 , 173 , 82 , 105 , 99 , 104 , 90 , 58 , 232 , 173 , 0 , 0 ,
201
+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
202
+ 80 , 69 , 0 , 0 , 100 , 134 , 7 , 0 , 29 , 151 , 54 , 91 , 0 , 0 , 0 , 0 , 0 , 0 ,
203
+ 0 , 0 , 240 , 0 , 34 , 0 , 11 , 2 , 14 , 0 , 0 , 28 , 1 , 0 , 0 , 8 , 1 , 0 , 0 , 0 ,
204
+ 0 , 0 , 80 , 231 , 0 , 0 , 0 , 16 , 0 , 0 , 0 , 0 , 0 , 64 , 1 , 0 , 0 , 0 , 0 , 16 ,
205
+ 0 , 0 , 0 , 2 , 0 , 0 , 6 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 6 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
206
+ 0 , 112 , 2 , 0 , 0 , 4 , 0 , 0 , 0 , 0 , 0 , 0 , 3 , 0 , 96 , 193 , 0 , 0 , 24 ,
207
+ 0 , 0 , 0 , 0 , 0 , 0 , 16 , 0 , 0 , 0 , 0 } ;
208
+
209
+ private class TestDirectory : IDisposable
210
+ {
211
+ public string Path { get ; private set ; }
212
+
213
+ private TestDirectory ( string path )
214
+ {
215
+ Path = path ;
216
+ Directory . CreateDirectory ( path ) ;
217
+ }
218
+
219
+ public static TestDirectory Create ( [ CallerMemberName ] string callingMethod = "" )
220
+ {
221
+ string path = System . IO . Path . Combine (
222
+ System . IO . Path . GetTempPath ( ) ,
223
+ "dotNetSdkUnitTest_" + callingMethod + ( Guid . NewGuid ( ) . ToString ( ) . Substring ( 0 , 8 ) ) ) ;
224
+ return new TestDirectory ( path ) ;
225
+ }
226
+
227
+ public void Dispose ( )
228
+ {
229
+ if ( Directory . Exists ( Path ) )
230
+ {
231
+ Directory . Delete ( Path , true ) ;
232
+ }
233
+ }
234
+ }
235
+ }
236
+ }
0 commit comments