/
GenerateBindingRedirects_Tests.cs
407 lines (344 loc) · 17.6 KB
/
GenerateBindingRedirects_Tests.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.UnitTests;
using Microsoft.Build.Utilities;
using Shouldly;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.Build.Tasks.UnitTests
{
public class GenerateBindingRedirectsTests : IDisposable
{
private readonly ITestOutputHelper _output;
private readonly TestEnvironment _env;
public GenerateBindingRedirectsTests(ITestOutputHelper output)
{
_output = output;
_env = TestEnvironment.Create(output);
}
public void Dispose()
{
_env.Dispose();
}
/// <summary>
/// In this case,
/// - A valid redirect information is provided for <see cref="GenerateBindingRedirects"/> task.
/// Expected:
/// - Task should create a target app.config with specified redirect information.
/// Rationale:
/// - The only goal for <see cref="GenerateBindingRedirects"/> task is to add specified redirects to the output app.config.
/// </summary>
[Fact]
[Trait("Category", "mono-osx-failing")]
public void TargetAppConfigShouldContainsBindingRedirects()
{
// Arrange
// Current appConfig is empty
string appConfigFile = WriteAppConfigRuntimeSection(string.Empty);
TaskItemMock redirect = new TaskItemMock("System, Version=10.0.0.0, Culture=Neutral, PublicKeyToken='b77a5c561934e089'", "40.0.0.0");
// Act
var redirectResults = GenerateBindingRedirects(appConfigFile, null, redirect);
// Assert
redirectResults.ExecuteResult.ShouldBeTrue();
redirectResults.TargetAppConfigContent.ShouldContain("<assemblyIdentity name=\"System\" publicKeyToken=\"b77a5c561934e089\" culture=\"neutral\" />");
redirectResults.TargetAppConfigContent.ShouldContain("newVersion=\"40.0.0.0\"");
}
/// <summary>
/// In this case,
/// - A valid redirect information is provided for <see cref="GenerateBindingRedirects"/> task and app.config is not empty.
/// Expected:
/// - Task should create a target app.config with specified redirect information.
/// Rationale:
/// - The only goal for <see cref="GenerateBindingRedirects"/> task is to add specified redirects to the output app.config.
/// </summary>
[Fact]
[Trait("Category", "mono-osx-failing")]
public void TargetAppConfigShouldContainsBindingRedirectsFromAppConfig()
{
// Arrange
string appConfigFile = WriteAppConfigRuntimeSection(
@"<assemblyBinding xmlns=""urn:schemas-microsoft-com:asm.v1"">
<dependentAssembly>
<assemblyIdentity name=""MyAssembly""
publicKeyToken = ""14a739be0244c389""
culture = ""Neutral""/>
<bindingRedirect oldVersion= ""1.0.0.0""
newVersion = ""5.0.0.0"" />
</dependentAssembly>
</assemblyBinding>");
TaskItemMock redirect = new TaskItemMock("MyAssembly, Version=10.0.0.0, Culture=Neutral, PublicKeyToken='14a739be0244c389'", "40.0.0.0");
// Act
var redirectResults = GenerateBindingRedirects(appConfigFile, null, redirect);
// Assert
redirectResults.TargetAppConfigContent.ShouldContain("MyAssembly");
redirectResults.TargetAppConfigContent.ShouldContain("<bindingRedirect oldVersion=\"0.0.0.0-40.0.0.0\" newVersion=\"40.0.0.0\"");
}
/// <summary>
/// In this case,
/// - An app.config is passed in with two dependentAssembly elements
/// Expected:
/// - Both redirects appears in the output app.config
/// Rationale:
/// - assemblyBinding could have more than one dependentAssembly elements and <see cref="GenerateBindingRedirects"/>
/// should respect that.
/// </summary>
[Fact]
[Trait("Category", "mono-osx-failing")]
public void GenerateBindingRedirectsFromTwoDependentAssemblySections()
{
// Arrange
string appConfigFile = WriteAppConfigRuntimeSection(
@"<loadFromRemoteSources enabled=""true""/>
<assemblyBinding xmlns=""urn:schemas-microsoft-com:asm.v1"" >
<dependentAssembly>
<assemblyIdentity name=""Microsoft.ServiceBus"" publicKeyToken =""31bf3856ad364e35"" culture =""neutral"" />
<bindingRedirect oldVersion=""2.0.0.0-3.0.0.0"" newVersion =""2.2.0.0"" />
</dependentAssembly>
<probing privatePath=""VSO14"" />
<dependentAssembly>
<assemblyIdentity name=""System.Web.Http"" publicKeyToken =""31bf3856ad364e35"" culture =""neutral"" />
<bindingRedirect oldVersion=""4.0.0.0-6.0.0.0"" newVersion =""4.0.0.0"" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name=""Microsoft.TeamFoundation.Common"" publicKeyToken =""b03f5f7f11d50a3a"" culture =""neutral"" />
<codeBase version=""11.0.0.0"" href =""Microsoft.TeamFoundation.Common.dll"" />
<codeBase version=""14.0.0.0"" href =""VSO14\Microsoft.TeamFoundation.Common.dll"" />
</dependentAssembly>
</assemblyBinding>");
TaskItemMock serviceBusRedirect = new TaskItemMock("Microsoft.ServiceBus, Version=2.0.0.0, Culture=Neutral, PublicKeyToken='31bf3856ad364e35'", "41.0.0.0");
TaskItemMock webHttpRedirect = new TaskItemMock("System.Web.Http, Version=4.0.0.0, Culture=Neutral, PublicKeyToken='31bf3856ad364e35'", "40.0.0.0");
// Act
var redirectResults = GenerateBindingRedirects(appConfigFile, null, serviceBusRedirect, webHttpRedirect);
// Assert
redirectResults.ExecuteResult.ShouldBeTrue();
// Naive check that target app.config contains custom redirects.
// Output config should have max versions for both serviceBus and webhttp assemblies.
redirectResults.TargetAppConfigContent.ShouldContain($"oldVersion=\"0.0.0.0-{serviceBusRedirect.MaxVersion}\"");
redirectResults.TargetAppConfigContent.ShouldContain($"newVersion=\"{serviceBusRedirect.MaxVersion}\"");
redirectResults.TargetAppConfigContent.ShouldContain($"oldVersion=\"0.0.0.0-{webHttpRedirect.MaxVersion}\"");
redirectResults.TargetAppConfigContent.ShouldContain($"newVersion=\"{webHttpRedirect.MaxVersion}\"");
XElement targetAppConfig = XElement.Parse(redirectResults.TargetAppConfigContent);
targetAppConfig.Descendants()
.Count(e => e.Name.LocalName.Equals("assemblyBinding", StringComparison.OrdinalIgnoreCase))
.ShouldBe(1);
// "Binding redirects should not add additional assemblyBinding sections into the target app.config: " + targetAppConfig
// Log file should contains a warning when GenerateBindingRedirects updates existing app.config entries
redirectResults.Engine.AssertLogContains("MSB3836");
}
/// <summary>
/// In this case,
/// - An app.config is passed in that has dependentAssembly section with probing element but without
/// assemblyIdentity or bindingRedirect elements.
/// Expected:
/// - No warning
/// Rationale:
/// - In initial implementation such app.config was considered invalid and MSB3835 was issued.
/// But due to MSDN documentation, dependentAssembly could have only probing element without any other elements inside.
/// </summary>
[Fact]
[Trait("Category", "mono-osx-failing")]
public void AppConfigWithProbingPathAndWithoutDependentAssemblyShouldNotProduceWarningsBug1161241()
{
// Arrange
string appConfigFile = WriteAppConfigRuntimeSection(
@"<assemblyBinding xmlns=""urn:schemas-microsoft-com:asm.v1"">
<probing privatePath = 'bin;bin2\subbin;bin3'/>
</assemblyBinding>");
TaskItemMock redirect = new TaskItemMock("System, Version=10.0.0.0, Culture=Neutral, PublicKeyToken='b77a5c561934e089'", "40.0.0.0");
// Act
var redirectResults = GenerateBindingRedirects(appConfigFile, null, redirect);
// Assert
redirectResults.Engine.Errors.ShouldBe(0); // "Unexpected errors. Engine log: " + redirectResults.Engine.Log
redirectResults.Engine.Warnings.ShouldBe(0); // "Unexpected errors. Engine log: " + redirectResults.Engine.Log
}
/// <summary>
/// In this case,
/// - An app.config is passed in that has empty assemblyBinding section.
/// Expected:
/// - No warning
/// Rationale:
/// - In initial implementation such app.config was considered invalid and MSB3835 was issued.
/// But due to MSDN documentation, dependentAssembly could have only probing element without any other elements inside.
/// </summary>
[Fact]
[Trait("Category", "mono-osx-failing")]
public void AppConfigWithEmptyAssemblyBindingShouldNotProduceWarnings()
{
// Arrange
string appConfigFile = WriteAppConfigRuntimeSection(
@"<assemblyBinding xmlns=""urn:schemas-microsoft-com:asm.v1"" appliesTo=""v1.0.3705"">
</assemblyBinding>");
TaskItemMock redirect = new TaskItemMock("System, Version=10.0.0.0, Culture=Neutral, PublicKeyToken='b77a5c561934e089'", "40.0.0.0");
// Act
var redirectResults = GenerateBindingRedirects(appConfigFile, null, redirect);
// Assert
redirectResults.Engine.Errors.ShouldBe(0);
redirectResults.Engine.Warnings.ShouldBe(0);
}
/// <summary>
/// In this case,
/// - An app.config is passed in that has dependentAssembly section with assemblyIdentity but without bindingRedirect
/// Expected:
/// - No warning
/// Rationale:
/// - Due to app.config xsd schema this is a valid configuration.
/// </summary>
[Fact]
[Trait("Category", "mono-osx-failing")]
public void DependentAssemblySectionWithoutBindingRedirectShouldNotProduceWarnings()
{
// Arrange
string appConfigFile = WriteAppConfigRuntimeSection(
@"<assemblyBinding xmlns=""urn:schemas-microsoft-com:asm.v1"" appliesTo=""v1.0.3705"">
<dependentAssembly>
<assemblyIdentity name=""Microsoft.TeamFoundation.Common"" publicKeyToken =""b03f5f7f11d50a3a"" culture =""neutral"" />
<codeBase version=""11.0.0.0"" href =""Microsoft.TeamFoundation.Common.dll"" />
<codeBase version=""14.0.0.0"" href =""VSO14\Microsoft.TeamFoundation.Common.dll"" />
</dependentAssembly>
</assemblyBinding>");
TaskItemMock redirect = new TaskItemMock("System, Version=10.0.0.0, Culture=Neutral, PublicKeyToken='b77a5c561934e089'", "40.0.0.0");
// Act
var redirectResults = GenerateBindingRedirects(appConfigFile, null, redirect);
// Assert
redirectResults.Engine.Errors.ShouldBe(0);
redirectResults.Engine.Warnings.ShouldBe(0);
}
/// <summary>
/// In this case,
/// - An app.config is passed in but dependentAssembly element is empty.
/// Expected:
/// - MSB3835
/// Rationale:
/// - Due to MSDN documentation, assemblyBinding element should always have a dependentAssembly subsection.
/// </summary>
[Fact]
public void AppConfigInvalidIfDependentAssemblyNodeIsEmpty()
{
// Construct the app.config.
string appConfigFile = WriteAppConfigRuntimeSection(
@"<assemblyBinding xmlns=""urn:schemas-microsoft-com:asm.v1"">
<dependentAssembly>
</dependentAssembly>
</assemblyBinding>");
TaskItemMock redirect = new TaskItemMock("System, Version=10.0.0.0, Culture=Neutral, PublicKeyToken='b77a5c561934e089'", "40.0.0.0");
// Act
var redirectResults = GenerateBindingRedirects(appConfigFile, null, redirect);
// Assert
redirectResults.Engine.AssertLogContains("MSB3835");
}
[Fact]
public void AppConfigFileNotSavedWhenIdentical()
{
string appConfigFile = WriteAppConfigRuntimeSection(string.Empty);
string outputAppConfigFile = _env.ExpectFile(".config").Path;
TaskItemMock redirect = new TaskItemMock("System, Version=10.0.0.0, Culture=Neutral, PublicKeyToken='b77a5c561934e089'", "40.0.0.0");
var redirectResults = GenerateBindingRedirects(appConfigFile, outputAppConfigFile, redirect);
// Verify it ran correctly
redirectResults.ExecuteResult.ShouldBeTrue();
redirectResults.TargetAppConfigContent.ShouldContain("<assemblyIdentity name=\"System\" publicKeyToken=\"b77a5c561934e089\" culture=\"neutral\" />");
redirectResults.TargetAppConfigContent.ShouldContain("newVersion=\"40.0.0.0\"");
var oldTimestamp = DateTime.Now.Subtract(TimeSpan.FromDays(30));
File.SetCreationTime(outputAppConfigFile, oldTimestamp);
File.SetLastWriteTime(outputAppConfigFile, oldTimestamp);
// Make sure it's old
File.GetCreationTime(outputAppConfigFile).ShouldBe(oldTimestamp, TimeSpan.FromSeconds(5));
File.GetLastWriteTime(outputAppConfigFile).ShouldBe(oldTimestamp, TimeSpan.FromSeconds(5));
// Re-run the task
var redirectResults2 = GenerateBindingRedirects(appConfigFile, outputAppConfigFile, redirect);
// Verify it ran correctly and that it's still old
redirectResults2.ExecuteResult.ShouldBeTrue();
redirectResults2.TargetAppConfigContent.ShouldContain("<assemblyIdentity name=\"System\" publicKeyToken=\"b77a5c561934e089\" culture=\"neutral\" />");
redirectResults.TargetAppConfigContent.ShouldContain("newVersion=\"40.0.0.0\"");
File.GetCreationTime(outputAppConfigFile).ShouldBe(oldTimestamp, TimeSpan.FromSeconds(5));
File.GetLastWriteTime(outputAppConfigFile).ShouldBe(oldTimestamp, TimeSpan.FromSeconds(5));
}
private BindingRedirectsExecutionResult GenerateBindingRedirects(string appConfigFile, string targetAppConfigFile,
params ITaskItem[] suggestedRedirects)
{
// Create the engine.
MockEngine engine = new MockEngine(_output);
string outputAppConfig = string.IsNullOrEmpty(targetAppConfigFile) ? _env.ExpectFile(".config").Path : targetAppConfigFile;
GenerateBindingRedirects bindingRedirects = new GenerateBindingRedirects
{
BuildEngine = engine,
SuggestedRedirects = suggestedRedirects ?? new ITaskItem[] { },
AppConfigFile = new TaskItem(appConfigFile),
OutputAppConfigFile = new TaskItem(outputAppConfig)
};
bool executionResult = bindingRedirects.Execute();
return new BindingRedirectsExecutionResult
{
ExecuteResult = executionResult,
Engine = engine,
SourceAppConfigContent = File.ReadAllText(appConfigFile),
TargetAppConfigContent = File.ReadAllText(outputAppConfig),
TargetAppConfigFilePath = outputAppConfig
};
}
private string WriteAppConfigRuntimeSection(string runtimeSection)
{
string formatString =
@"<configuration>
<runtime>
{0}
</runtime>
</configuration>";
string appConfigContents = string.Format(formatString, runtimeSection);
string appConfigFile = _env.CreateFile(".config").Path;
File.WriteAllText(appConfigFile, appConfigContents);
return appConfigFile;
}
/// <summary>
/// Helper class that contains execution results for <see cref="GenerateBindingRedirects"/>.
/// </summary>
private class BindingRedirectsExecutionResult
{
public MockEngine Engine { get; set; }
public string SourceAppConfigContent { get; set; }
public string TargetAppConfigContent { get; set; }
public bool ExecuteResult { get; set; }
public string TargetAppConfigFilePath { get; set; }
}
/// <summary>
/// Mock implementation of the <see cref="ITaskItem"/>.
/// </summary>
private class TaskItemMock : ITaskItem
{
public TaskItemMock(string assemblyName, string maxVersion)
{
((ITaskItem)this).ItemSpec = assemblyName;
MaxVersion = maxVersion;
}
public string MaxVersion { get; }
string ITaskItem.ItemSpec { get; set; }
ICollection ITaskItem.MetadataNames { get; }
int ITaskItem.MetadataCount { get; }
string ITaskItem.GetMetadata(string metadataName)
{
return MaxVersion;
}
void ITaskItem.SetMetadata(string metadataName, string metadataValue)
{
throw new NotImplementedException();
}
void ITaskItem.RemoveMetadata(string metadataName)
{
throw new NotImplementedException();
}
void ITaskItem.CopyMetadataTo(ITaskItem destinationItem)
{
throw new NotImplementedException();
}
IDictionary ITaskItem.CloneCustomMetadata()
{
throw new NotImplementedException();
}
}
}
}