성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>Visual Studio - Fine Code Coverage에서 동작하지 않는 Fake/Shim 테스트</h1> <p> 이런 코드 커버리지 도구가 있군요.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Fine Code Coverage ; <a target='tab' href='https://marketplace.visualstudio.com/items?itemName=FortuneNgwenya.FineCodeCoverage'>https://marketplace.visualstudio.com/items?itemName=FortuneNgwenya.FineCodeCoverage</a> </pre> <br /> 음... 별로 매력적인 부분을 찾을 수 없는데, 왜 Visual Studio의 기본 Code Coverage를 놔두고 이걸 사용하는 걸까요? 혹시 장점을 아시는 분은 덧글 부탁드립니다. ^^<br /> <br /> 일단, 설치는 Visual Studio 2022에서도 할 수 있습니다. 그런데, 이게 한 가지 문제가 있는데요, 바로 Fake/Shim을 지원하지 못한다는 점입니다.<br /> <br /> 실제로 <a target='tab' href='https://www.sysnet.pe.kr/2/0/13327'>해당 코드가 들어간 단위 테스트를 작성</a>하면, FCC 창에서 다음과 같은 오류를 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Failed TestTest [19 ms] Error Message: Test method ClassLibrary1.Tests.Class1Tests.TestTest threw exception: Microsoft.QualityTools.Testing.Fakes.UnitTestIsolation.UnitTestIsolationException: Failed to resolve profiler path from COR_PROFILER_PATH and COR_PROFILER environment variables. Stack Trace: at Microsoft.QualityTools.Testing.Fakes.UnitTestIsolation.IntelliTraceInstrumentationProvider.ResolveProfilerPath() at Microsoft.QualityTools.Testing.Fakes.UnitTestIsolation.IntelliTraceInstrumentationProvider.Initialize() at Microsoft.QualityTools.Testing.Fakes.UnitTestIsolation.UnitTestIsolationRuntime.InitializeUnitTestIsolationInstrumentationProvider() at Microsoft.QualityTools.Testing.Fakes.Shims.ShimRuntime.CreateContext() at Microsoft.QualityTools.Testing.Fakes.ShimsContext.Create() at ClassLibrary1.Tests.Class1Tests.TestTest() in C:\Users\SeongTae Jeong\Dropbox\articles\fin_code_coverage\net6_fakes_sample\ClassLibrary1Tests\Class1Tests.cs:line 19 Failed! - Failed: 1, Passed: 1, Skipped: 0, Total: 2, Duration: 42 ms - ClassLibrary1Tests.dll (net6.0) </pre> <br /> 왜냐하면 Fake/Shim은 .NET Profiler의 IL Rewriter 기능을 이용해 작성되기 때문인데, 저렇게 Profiler 로딩을 못하므로 실패한 것입니다. 아마도 "{324F817A-7420-4E6D-B3C1-143FBED6D855}" GUID에 해당하는 Profiler로 여겨지는데, Visual Studio에서 제공하는 Code Coverage에서는 저 프로파일러가 잘 로딩이 되지만 Fine Code Coverage에서는 로딩에 실패하고 있는 것입니다. (이유는 잘 모르겠습니다.)<br /> <br /> 암튼, 저렇게 되면 ShimsContext.Create 호출부터 오류가 발생할 것이기 때문에 Code Coverage가 제대로 될 수 없습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 약간의 원인 분석을 해보자면.<br /> <br /> Visual Studio가 실행하는 vstest.console.exe는 하위 프로세스로 testhost.exe를 통해 단위 테스트를 수행합니다. 이때 해당 프로세스에는 PROFILER 관련 환경 변수 설정들이 동적으로 추가되는데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > SET CORECLR_PROFILER={324F817A-7420-4E6D-B3C1-143FBED6D855} SET CORECLR_PROFILER_PATH_64=C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\CommonExtensions\Platform\InstrumentationEngine\x64\MicrosoftInstrumentationEngine_x64.dll SET CORECLR_ENABLE_PROFILING=1 SET COR_PROFILER={324F817A-7420-4E6D-B3C1-143FBED6D855} SET COR_PROFILER_PATH_64=C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\CommonExtensions\Platform\InstrumentationEngine\x64\MicrosoftInstrumentationEngine_x64.dll SET COR_ENABLE_PROFILING=1 SET MicrosoftInstrumentationEngine_ConfigPath64_FakesInstrumentation=C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Fakes\x64\FakesInstrumentationProfiler_x64.config /* FakesInstrumentationProfiler_x64.config <?xml version="1.0" encoding="utf-8"?> <InstrumentationEngineConfiguration> <InstrumentationMethod> <Name>Fakes Instrumentation Method</Name> <Description>Instrumentation method to support Microsoft Fakes</Description> <Module>Microsoft.QualityTools.Testing.Fakes.Instrumentation.dll</Module> <ClassGuid>{F02C3E96-F6FD-4552-9544-9F06BE6E5A0B}</ClassGuid> <Priority>11</Priority> </InstrumentationMethod> </InstrumentationEngineConfiguration> */ </pre> <br /> 반면 FineCodeCoverage는 coverlet.exe를 통해 몇 층의 dotnet.exe를 거쳐 testhost.exe를 실행하게 되는데 이 계층 구조에서는 당연히 PROFILER 관련 환경 변수 설정이 없습니다. 그래서, 혹시나 싶어 명령행을 실행해 위의 COR_..., CORECLR_... 환경 변수 설정을 한 후 이를 상속받을 수 있도록 devenv.exe를 실행시켜 Code Coverage를 수행해 봤습니다.<br /> <br /> 그래서 분명히 환경 변수가 적용까지는 되지만 아쉽게도, 여전히 이런 오류가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Failed TestTest [26 s] Error Message: Test method ClassLibrary1.Tests.Class1Tests.TestTest threw exception: Microsoft.QualityTools.Testing.Fakes.UnitTestIsolation.UnitTestIsolationException: Unexpected error returned by SetDetourProvider in profiler library 'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Fakes\x64\Microsoft.QualityTools.Testing.Fakes.Instrumentation.dll'. Stack Trace: at Microsoft.QualityTools.Testing.Fakes.UnitTestIsolation.IntelliTraceInstrumentationProvider.Initialize() at Microsoft.QualityTools.Testing.Fakes.UnitTestIsolation.UnitTestIsolationRuntime.InitializeUnitTestIsolationInstrumentationProvider() at Microsoft.QualityTools.Testing.Fakes.Shims.ShimRuntime.CreateContext() at Microsoft.QualityTools.Testing.Fakes.ShimsContext.Create() at ClassLibrary1.Tests.Class1Tests.TestTest() in C:\temp2\ClassLibrary1Tests\Class1Tests.cs:line 27 Failed! - Failed: 1, Passed: 1, Skipped: 0, Total: 2, Duration: 26 s - ClassLibrary1Tests.dll (net6.0) </pre> <br /> 이때 뜬 FineCodeCoverage의 testhost.exe에는 MicrosoftInstrumentationEngine_x64.dll과, "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Fakes\x64\Microsoft.QualityTools.Testing.Fakes.Instrumentation.dll" 파일이 로딩된 것을 확인할 수 있습니다.<br /> <br /> 그런데 웬일인지 SetDetourProvider를 실행하지 못하고 있는 것입니다. 위의 코드가 실행되는 ".\bin\Debug\net6.0\Microsoft.QualityTools.Testing.Fakes.dll" 파일을 Reflector로 열어 보면 다음의 코드에서 오류가 발생하고 있는데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // Microsoft.QualityTools.Testing.Fakes.UnitTestIsolation.IntelliTraceInstrumentationProvider // Token: 0x060000FE RID: 254 RVA: 0x0000382C File Offset: 0x00001A2C public void Initialize() { string text = this.<span style='color: blue; font-weight: bold'>ResolveProfilerPath</span>(); this.profilerModule = IntelliTraceInstrumentationProvider.LoadProfilerModule(text); <span style='color: blue; font-weight: bold'>this.setDetourProvider = LibraryMethods.GetFunction<NativeSetDetourProvider>(this.profilerModule, "SetDetourProvider");</span> this.canDetour = LibraryMethods.GetFunction<NativeCanDetour>(this.profilerModule, "CanDetour"); if (this.setDetourProvider(IntelliTraceInstrumentationProvider.detourProviderAddress) != 0) { <span style='color: blue; font-weight: bold'>throw new UnitTestIsolationException(string.Format(CultureInfo.CurrentCulture, FakesFrameworkResources.FailedToSetDetourProvider, text));</span> } this.enabled = true; } public static T GetFunction<T>(IntPtr hModule, string functionName) where T : class { <span style='color: blue; font-weight: bold'>return (T)((object)Marshal.GetDelegateForFunctionPointer(LibraryMethods.GetProcAddress(hModule, functionName), typeof(T)));</span> } </pre> <br /> ResolveProfilerPath의 코드를 통해,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > private string ResolveProfilerPath() { string environmentVariable = Environment.GetEnvironmentVariable((IntPtr.Size == 8) ? "MicrosoftInstrumentationEngine_ConfigPath64_FakesInstrumentation" : "MicrosoftInstrumentationEngine_ConfigPath32_FakesInstrumentation", EnvironmentVariableTarget.Process); if (File.Exists(environmentVariable)) { XmlReaderSettings settings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Prohibit, XmlResolver = null }; XmlReader xmlReader = XmlReader.Create(environmentVariable, settings); <span style='color: blue; font-weight: bold'>if (xmlReader.ReadToDescendant("Module"))</span> { string path = xmlReader.ReadInnerXml(); return Path.Combine(Path.GetDirectoryName(environmentVariable), path); } } ...[생략]... throw new UnitTestIsolationException(FakesFrameworkResources.FailedToResolveProfilerPath); } </pre> <br /> FakesInstrumentationProfiler_x64.config에 있는 Module 노드의 값, 즉 "Microsoft.QualityTools.Testing.Fakes.Instrumentation"을 가져오게 되고, 결국 다음의 DLL에서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Fakes\x64\Microsoft.QualityTools.Testing.Fakes.Instrumentation.dll </pre> <br /> SetDetourProvider를 구하게 됩니다. 물론, 이 파일에는,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C:\temp> dumpbin /EXPORTS "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Fakes\x64\Microsoft.QualityTools.Testing.Fakes.Instrumentation.dll" ...[생략]... ordinal hint RVA name 1 0 000088A0 <span style='color: blue; font-weight: bold'>CanDetour</span> 2 1 00001920 DllCanUnloadNow 3 2 00001930 DllGetClassObject 4 3 00001920 DllRegisterServer 5 4 00001920 DllUnregisterServer 6 5 00008800 <span style='color: blue; font-weight: bold'>SetDetourProvider</span> ...[생략]... </pre> <br /> 해당 API 함수를 정상적으로 export 하고 있습니다. 따라서 거기까지는 완료를 했는데, 아쉽게도 this.setDetourProvider 호출에서 오류가 발생한 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // Microsoft.QualityTools.Testing.Fakes.UnitTestIsolation.IntelliTraceInstrumentationProvider // Token: 0x060000FE RID: 254 RVA: 0x0000382C File Offset: 0x00001A2C public void Initialize() { string text = this.ResolveProfilerPath(); this.profilerModule = IntelliTraceInstrumentationProvider.LoadProfilerModule(text); this.setDetourProvider = LibraryMethods.GetFunction<NativeSetDetourProvider>(this.profilerModule, "SetDetourProvider"); this.canDetour = LibraryMethods.GetFunction<NativeCanDetour>(this.profilerModule, "CanDetour"); if (<span style='color: blue; font-weight: bold'>this.setDetourProvider(IntelliTraceInstrumentationProvider.detourProviderAddress) != 0</span>) { <span style='color: blue; font-weight: bold'>throw new UnitTestIsolationException(string.Format(CultureInfo.CurrentCulture, FakesFrameworkResources.FailedToSetDetourProvider, text));</span> } this.enabled = true; } </pre> <br /> 여기서 IntelliTraceInstrumentationProvider.detourProviderAddress가 가리키는 주소는, <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > private static readonly IntPtr detourProviderAddress = typeof(IntelliTraceInstrumentationProvider).GetMethod("DetourProvider", BindingFlags.Static | BindingFlags.NonPublic).MethodHandle.GetFunctionPointer(); </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > internal static void DetourProvider(object receiver, RuntimeMethodHandle methodHandle, RuntimeTypeHandle declaringTypeHandle, RuntimeTypeHandle[] genericMethodTypeArgumentHandles, out object detourDelegate, out IntPtr detourPointer) { detourDelegate = null; detourPointer = IntPtr.Zero; if (IntelliTraceInstrumentationProvider.ProtectingContext.IsThreadProtected) { return; } using (new IntelliTraceInstrumentationProvider.ProtectingContext()) { MethodBase methodBase = MethodBase.GetMethodFromHandle(methodHandle, declaringTypeHandle); if (methodBase.IsGenericMethodDefinition) { Type[] array = new Type[genericMethodTypeArgumentHandles.Length]; for (int i = 0; i < genericMethodTypeArgumentHandles.Length; i++) { array[i] = Type.GetTypeFromHandle(genericMethodTypeArgumentHandles[i]); } methodBase = ((MethodInfo)methodBase).MakeGenericMethod(array); } detourDelegate = UnitTestIsolationRuntime.GetDetour(receiver, methodBase); if (detourDelegate != null) { detourPointer = detourDelegate.GetType().GetMethod("Invoke").MethodHandle.GetFunctionPointer(); } } } </pre> <br /> 위와 같은데, 따라서 SetDetourProvider에 저 메서드(DetourProvider)를 전달한 다음 내부에서 뭔가 동작이 있었는데 거기서 알 수 없는 오류가 발생하고 있는 것입니다. 일단, 더 이상 추적할 수 없으니, 안 되는 걸로 ^^ 종료합니다. 뭔가 잡힐 듯하다가 놓치고 마니 아쉽군요.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
2038
(왼쪽의 숫자를 입력해야 합니다.)