|
| 1 | +using System; |
| 2 | +using System.CodeDom.Compiler; |
| 3 | +using System.IO; |
| 4 | +using System.Linq; |
| 5 | +using System.Reflection; |
| 6 | +using Microsoft.CSharp; |
1 | 7 | using Newtonsoft.Json.Linq; |
2 | 8 | using NUnit.Framework; |
3 | 9 | using MCPForUnity.Editor.Tools; |
@@ -367,8 +373,170 @@ public void Execute_CodedomBackend_ResolvesUnityTypes() |
367 | 373 | Assert.IsNotNull(result["data"]["result"]); |
368 | 374 | } |
369 | 375 |
|
| 376 | + [Test] |
| 377 | + public void FilterAssemblyPathsForCodeDom_WithNetstandard_PreservesSystemSecurity() |
| 378 | + { |
| 379 | + var tempRoot = CreateTempDirectory(); |
| 380 | + try |
| 381 | + { |
| 382 | + var netstandardPath = CompileVersionedAssembly(tempRoot, "netstandard", "2.0.0.0"); |
| 383 | + var securityFixturePath = CompileVersionedAssembly(tempRoot, "SystemSecurityFixture", "4.0.0.0"); |
| 384 | + var systemSecurityPath = Path.Combine( |
| 385 | + Path.GetDirectoryName(securityFixturePath), |
| 386 | + "System.Security.dll"); |
| 387 | + File.Copy(securityFixturePath, systemSecurityPath); |
| 388 | + |
| 389 | + var filtered = ExecuteCode.FilterAssemblyPathsForCodeDom(new[] |
| 390 | + { |
| 391 | + netstandardPath, |
| 392 | + systemSecurityPath, |
| 393 | + }); |
| 394 | + |
| 395 | + CollectionAssert.Contains(filtered, systemSecurityPath); |
| 396 | + } |
| 397 | + finally |
| 398 | + { |
| 399 | + Directory.Delete(tempRoot, true); |
| 400 | + } |
| 401 | + } |
| 402 | + |
| 403 | + [Test] |
| 404 | + public void FilterAssemblyPathsForCodeDom_DuplicateNames_PrefersReferencedVersion() |
| 405 | + { |
| 406 | + var tempRoot = CreateTempDirectory(); |
| 407 | + try |
| 408 | + { |
| 409 | + var assemblyName = "McpCodeDomDuplicate" + Guid.NewGuid().ToString("N"); |
| 410 | + var referencedPath = CompileVersionedAssembly(tempRoot, assemblyName, "1.0.0.0"); |
| 411 | + var newerPath = CompileVersionedAssembly(tempRoot, assemblyName, "2.0.0.0"); |
| 412 | + LoadAssemblyReferencing(referencedPath); |
| 413 | + |
| 414 | + var filtered = ExecuteCode.FilterAssemblyPathsForCodeDom(new[] |
| 415 | + { |
| 416 | + newerPath, |
| 417 | + referencedPath, |
| 418 | + }); |
| 419 | + |
| 420 | + Assert.AreEqual(1, filtered.Length); |
| 421 | + Assert.AreEqual(referencedPath, filtered[0]); |
| 422 | + } |
| 423 | + finally |
| 424 | + { |
| 425 | + Directory.Delete(tempRoot, true); |
| 426 | + } |
| 427 | + } |
| 428 | + |
| 429 | + [Test] |
| 430 | + public void FilterAssemblyPathsForCodeDom_CachedAssemblyPaths_ReusesResultUntilDomainReload() |
| 431 | + { |
| 432 | + var tempRoot = CreateTempDirectory(); |
| 433 | + var cachedAssemblyPathsField = typeof(ExecuteCode).GetField( |
| 434 | + "_cachedAssemblyPaths", |
| 435 | + BindingFlags.NonPublic | BindingFlags.Static); |
| 436 | + var cachedCodeDomAssemblyPathsField = typeof(ExecuteCode).GetField( |
| 437 | + "_cachedCodeDomAssemblyPaths", |
| 438 | + BindingFlags.NonPublic | BindingFlags.Static); |
| 439 | + var onDomainReload = typeof(ExecuteCode).GetMethod( |
| 440 | + "OnDomainReload", |
| 441 | + BindingFlags.NonPublic | BindingFlags.Static); |
| 442 | + Assert.IsNotNull(cachedAssemblyPathsField); |
| 443 | + Assert.IsNotNull(cachedCodeDomAssemblyPathsField); |
| 444 | + Assert.IsNotNull(onDomainReload); |
| 445 | + |
| 446 | + try |
| 447 | + { |
| 448 | + onDomainReload.Invoke(null, null); |
| 449 | + var assemblyName = "McpCodeDomCache" + Guid.NewGuid().ToString("N"); |
| 450 | + var olderPath = CompileVersionedAssembly(tempRoot, assemblyName, "1.0.0.0"); |
| 451 | + var newerPath = CompileVersionedAssembly(tempRoot, assemblyName, "2.0.0.0"); |
| 452 | + var cachedAssemblyPaths = new[] { olderPath, newerPath }; |
| 453 | + cachedAssemblyPathsField.SetValue(null, cachedAssemblyPaths); |
| 454 | + |
| 455 | + var first = ExecuteCode.FilterAssemblyPathsForCodeDom(cachedAssemblyPaths); |
| 456 | + Assert.AreEqual(1, first.Length); |
| 457 | + |
| 458 | + File.WriteAllText(olderPath, "invalidated"); |
| 459 | + File.WriteAllText(newerPath, "invalidated"); |
| 460 | + var second = ExecuteCode.FilterAssemblyPathsForCodeDom(cachedAssemblyPaths); |
| 461 | + Assert.AreSame(first, second); |
| 462 | + |
| 463 | + onDomainReload.Invoke(null, null); |
| 464 | + cachedAssemblyPathsField.SetValue(null, cachedAssemblyPaths); |
| 465 | + var afterReload = ExecuteCode.FilterAssemblyPathsForCodeDom(cachedAssemblyPaths); |
| 466 | + Assert.AreNotSame(first, afterReload); |
| 467 | + Assert.AreEqual(2, afterReload.Length); |
| 468 | + } |
| 469 | + finally |
| 470 | + { |
| 471 | + onDomainReload.Invoke(null, null); |
| 472 | + Directory.Delete(tempRoot, true); |
| 473 | + } |
| 474 | + } |
| 475 | + |
370 | 476 | // ──────────────────── Helpers ──────────────────── |
371 | 477 |
|
| 478 | + private static string CreateTempDirectory() |
| 479 | + { |
| 480 | + var path = Path.Combine(Path.GetTempPath(), "UnityMCPTests", Guid.NewGuid().ToString("N")); |
| 481 | + Directory.CreateDirectory(path); |
| 482 | + return path; |
| 483 | + } |
| 484 | + |
| 485 | + private static string CompileVersionedAssembly(string tempRoot, string assemblyName, string version) |
| 486 | + { |
| 487 | + var outputDirectory = Path.Combine(tempRoot, version); |
| 488 | + Directory.CreateDirectory(outputDirectory); |
| 489 | + var outputPath = Path.Combine(outputDirectory, assemblyName + ".dll"); |
| 490 | + var source = |
| 491 | + "using System.Reflection;\n" + |
| 492 | + "[assembly: AssemblyVersion(\"" + version + "\")]\n" + |
| 493 | + "public sealed class VersionMarker { }"; |
| 494 | + |
| 495 | + using (var provider = new CSharpCodeProvider()) |
| 496 | + { |
| 497 | + var parameters = new CompilerParameters |
| 498 | + { |
| 499 | + GenerateExecutable = false, |
| 500 | + GenerateInMemory = false, |
| 501 | + OutputAssembly = outputPath, |
| 502 | + }; |
| 503 | + var results = provider.CompileAssemblyFromSource(parameters, source); |
| 504 | + AssertCompilerSuccess(results); |
| 505 | + } |
| 506 | + |
| 507 | + return outputPath; |
| 508 | + } |
| 509 | + |
| 510 | + private static void LoadAssemblyReferencing(string referencedAssemblyPath) |
| 511 | + { |
| 512 | + using (var provider = new CSharpCodeProvider()) |
| 513 | + { |
| 514 | + var parameters = new CompilerParameters |
| 515 | + { |
| 516 | + GenerateExecutable = false, |
| 517 | + GenerateInMemory = true, |
| 518 | + }; |
| 519 | + parameters.ReferencedAssemblies.Add(referencedAssemblyPath); |
| 520 | + |
| 521 | + var results = provider.CompileAssemblyFromSource( |
| 522 | + parameters, |
| 523 | + "public static class ReferenceHolder { " + |
| 524 | + "public static System.Type Get() { return typeof(VersionMarker); } }"); |
| 525 | + AssertCompilerSuccess(results); |
| 526 | + Assert.IsNotNull(results.CompiledAssembly); |
| 527 | + } |
| 528 | + } |
| 529 | + |
| 530 | + private static void AssertCompilerSuccess(CompilerResults results) |
| 531 | + { |
| 532 | + var errors = results.Errors |
| 533 | + .Cast<CompilerError>() |
| 534 | + .Where(error => !error.IsWarning) |
| 535 | + .Select(error => error.ToString()) |
| 536 | + .ToArray(); |
| 537 | + Assert.IsFalse(results.Errors.HasErrors, string.Join("\n", errors)); |
| 538 | + } |
| 539 | + |
372 | 540 | private static JObject Execute(string code) |
373 | 541 | { |
374 | 542 | return ToJObject(ExecuteCode.HandleCommand(new JObject |
|
0 commit comments