diff --git a/playground/TestPlatform.Playground/TestPlatform.Playground.csproj b/playground/TestPlatform.Playground/TestPlatform.Playground.csproj index 33f2378d48..ed6ad67876 100644 --- a/playground/TestPlatform.Playground/TestPlatform.Playground.csproj +++ b/playground/TestPlatform.Playground/TestPlatform.Playground.csproj @@ -47,7 +47,7 @@ - + diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/BlameCollector.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/BlameCollector.cs index 81de903e3c..6a2fd5ae18 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/BlameCollector.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/BlameCollector.cs @@ -8,11 +8,16 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading; using System.Xml; using Microsoft.VisualStudio.TestPlatform.Execution; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; +using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; +using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; using Microsoft.VisualStudio.TestPlatform.Utilities; using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; @@ -37,11 +42,13 @@ public class BlameCollector : DataCollector, ITestExecutionEnvironmentSpecifier private Dictionary? _testObjectDictionary; private readonly IBlameReaderWriter _blameReaderWriter; private readonly IFileHelper _fileHelper; + private readonly IProcessHelper _processHelper; private XmlElement? _configurationElement; private int _testStartCount; private int _testEndCount; private bool _collectProcessDumpOnCrash; private bool _collectProcessDumpOnHang; + private bool _monitorPostmortemDumpFolder; private bool _collectDumpAlways; private string? _attachmentGuid; @@ -53,17 +60,19 @@ public class BlameCollector : DataCollector, ITestExecutionEnvironmentSpecifier private TimeSpan _inactivityTimespan = TimeSpan.FromMinutes(DefaultInactivityTimeInMinutes); private int _testHostProcessId; + private string? _testHostProcessName; private string? _targetFramework; private readonly List> _environmentVariables = new(); private bool _uploadDumpFiles; private string? _tempDirectory; + private string? _monitorPostmortemDumpFolderPath; /// /// Initializes a new instance of the class. /// Using XmlReaderWriter by default /// public BlameCollector() - : this(new XmlReaderWriter(), new ProcessDumpUtility(), null, new FileHelper()) + : this(new XmlReaderWriter(), new ProcessDumpUtility(), null, new FileHelper(), new ProcessHelper()) { } @@ -86,12 +95,14 @@ internal BlameCollector( IBlameReaderWriter blameReaderWriter, IProcessDumpUtility processDumpUtility, IInactivityTimer? inactivityTimer, - IFileHelper fileHelper) + IFileHelper fileHelper, + IProcessHelper processHelper) { _blameReaderWriter = blameReaderWriter; _processDumpUtility = processDumpUtility; _inactivityTimer = inactivityTimer; _fileHelper = fileHelper; + _processHelper = processHelper; } /// @@ -172,6 +183,10 @@ public override void Initialize( _collectProcessDumpOnHang = false; } + _monitorPostmortemDumpFolder = _configurationElement[Constants.MonitorPostmortemDebugger] is XmlElement monitorPostmortemNode && + ValidateMonitorPostmortemDebuggerParameters(monitorPostmortemNode); + EqtTrace.Info($"[MonitorPostmortemDump]Monitor enabled: '{_monitorPostmortemDumpFolder}'"); + var tfm = _configurationElement[Constants.TargetFramework]?.InnerText; if (!tfm.IsNullOrWhiteSpace()) { @@ -310,6 +325,24 @@ private void CollectDumpAndAbortTesthost() } } + private bool ValidateMonitorPostmortemDebuggerParameters(XmlElement collectDumpNode) + { + TPDebug.Assert(_logger != null && _context != null, "Initialize must be called before calling this method"); + if (StringUtils.IsNullOrEmpty(_monitorPostmortemDumpFolderPath = collectDumpNode.GetAttribute("DumpDirectoryPath"))) + { + _logger.LogWarning(_context.SessionDataCollectionContext, Resources.Resources.MonitorPostmortemDebuggerInvalidDumpDirectoryPathParameter); + return false; + } + + if (!_fileHelper.DirectoryExists(_monitorPostmortemDumpFolderPath)) + { + _logger.LogWarning(_context.SessionDataCollectionContext, Resources.Resources.MonitorPostmortemDebuggerInvalidDumpDirectoryPathParameter); + return false; + } + + return true; + } + private void ValidateAndAddCrashProcessDumpParameters(XmlElement collectDumpNode) { TPDebug.Assert(_logger != null && _context != null, "Initialize must be called before calling this method"); @@ -466,7 +499,7 @@ private void EventsTestCaseEnd(object? sender, TestCaseEndEventArgs e) /// SessionEndEventArgs private void SessionEndedHandler(object? sender, SessionEndEventArgs args) { - TPDebug.Assert(_testSequence != null && _testObjectDictionary != null && _context != null && _dataCollectionSink != null && _logger != null, "Initialize must be called before calling this method"); + TPDebug.Assert(_testHostProcessName != null && _testSequence != null && _testObjectDictionary != null && _context != null && _dataCollectionSink != null && _logger != null, "Initialize must be called before calling this method"); ResetInactivityTimer(); EqtTrace.Info("Blame Collector: Session End"); @@ -525,6 +558,55 @@ private void SessionEndedHandler(object? sender, SessionEndEventArgs args) { EqtTrace.Info("BlameCollector.CollectDumpAndAbortTesthost: Custom path to dump directory was provided via VSTEST_DUMP_PATH. Skipping attachment upload, the caller is responsible for collecting and uploading the dumps themselves."); } + + if (_monitorPostmortemDumpFolder) + { + if (!_fileHelper.DirectoryExists(_monitorPostmortemDumpFolderPath)) + { + _logger.LogWarning(_context.SessionDataCollectionContext, Resources.Resources.MonitorPostmortemDebuggerInvalidDumpDirectoryPathParameter); + } + else + { + // We do ToArray() because we're moving files and we cannot move file and enumerate at the same time + foreach (var dumpFileNameFullPath in _fileHelper.GetFiles(_monitorPostmortemDumpFolderPath, "*.dmp", SearchOption.TopDirectoryOnly).ToArray()) + { + EqtTrace.Info($"[MonitorPostmortemDump]'{dumpFileNameFullPath}' dump file found during postmortem monitoring"); + // Ensure exclusive access to the dump file, it can happen if we run more test module in parallel. + // We cannot ensure that we'll move only "our" dump because procdump -i produce a name that doesn't have the pid in it(because PID is reusable). + // The name of the file starts with the process name, that's the only filtering we can do. + // So there's one possible benign race condition when another test is dumping an host and we take lock on the name but the dump is not finished. + // In that case we'll fail for file locking but it's fine. The correct or subsequent "SessionEndedHandler" will move that one. + using MD5 md5LockName = MD5.Create(); + // BitConverter converts into something like EC-1B-B6-22-81-00-41-C8-31-1D-B6-61-27-6A-65-8A valid muxer name + // LPCSTR An LPCSTR is a 32-bit pointer to a constant null-terminated string of 8-bit Windows (ANSI) characters. + string muxerName = @$"Global\{BitConverter.ToString(md5LockName.ComputeHash(Encoding.UTF8.GetBytes(dumpFileNameFullPath)))}"; + using Mutex lockFile = new(true, muxerName, out bool createdNew); + EqtTrace.Info($"[MonitorPostmortemDump]Acquired global muxer '{muxerName}' for {dumpFileNameFullPath}"); + if (createdNew) + { + string dumpFileName = Path.GetFileNameWithoutExtension(dumpFileNameFullPath); + + // Expected format testhost.exe_221004_123127.dmp processName.exe_yyMMdd_HHmmss.dmp + if (dumpFileName.StartsWith(_testHostProcessName, StringComparison.OrdinalIgnoreCase)) + { + EqtTrace.Info($"[MonitorPostmortemDump]Valid pattern start with '{_testHostProcessName}' found for {dumpFileNameFullPath}"); + try + { + var fileTranferInformation = new FileTransferInformation(_context.SessionDataCollectionContext, dumpFileNameFullPath, true); + EqtTrace.Info($"[MonitorPostmortemDump]Transferring {dumpFileNameFullPath}"); + _dataCollectionSink.SendFileAsync(fileTranferInformation); + } + catch (IOException ex) + { + // In case of race condition explained in the comment above we simply log a warning. + EqtTrace.Warning(ex.ToString()); + _logger.LogWarning(args.Context, ex.ToString()); + } + } + } + } + } + } } finally { @@ -547,6 +629,7 @@ private void TestHostLaunchedHandler(object? sender, TestHostLaunchedEventArgs a { ResetInactivityTimer(); _testHostProcessId = args.TestHostProcessId; + _testHostProcessName = _processHelper.GetProcessName(args.TestHostProcessId); if (!_collectProcessDumpOnCrash) { diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Constants.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Constants.cs index 03624ea500..68dc296e3d 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Constants.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Constants.cs @@ -83,6 +83,11 @@ internal static class Constants /// public const string CollectDumpOnTestSessionHang = "CollectDumpOnTestSessionHang"; + /// + /// Configuration key name for monitoring a folder for postmortem dumps + /// + public const string MonitorPostmortemDebugger = "MonitorPostmortemDebugger"; + /// /// Configuration key name for specifying what the expected execution time for the longest running test is. /// If no events come from the test host for this period a dump will be collected and the test host process will diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/Resources.Designer.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/Resources.Designer.cs index 5f49a6796f..2c32d6d3df 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/Resources.Designer.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/Resources.Designer.cs @@ -135,6 +135,15 @@ internal static string Minutes { } } + /// + /// Looks up a localized string similar to Invalid 'DumpDirectoryPath' for postmortem debugger monitor. + /// + internal static string MonitorPostmortemDebuggerInvalidDumpDirectoryPathParameter { + get { + return ResourceManager.GetString("MonitorPostmortemDebuggerInvalidDumpDirectoryPathParameter", resourceCulture); + } + } + /// /// Looks up a localized string similar to All tests finished running, Sequence file will not be generated. /// diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/Resources.resx b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/Resources.resx index d8541c67d2..b1a62c3790 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/Resources.resx +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/Resources.resx @@ -171,4 +171,7 @@ This test may, or may not be the source of the crash. Unexpected value '{0}' provided as timeout. Please provide a value in this format: 1.5h / 90m / 5400s / 5400000ms / 5400000 - \ No newline at end of file + + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + + diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.cs.xlf b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.cs.xlf index bd67498cad..d1b24dfcce 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.cs.xlf +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.cs.xlf @@ -83,6 +83,11 @@ Tento test může a nemusí být příčinou chybového ukončení. {0} nelze najít. Zkontrolujte, zda je spustitelný soubor k dispozici v cestě PATH, případně nastavte proměnnou prostředí PROCDUMP_PATH na adresář, který obsahuje spustitelný soubor {0}. 0 is procDumpPath, a name of procdump executable such as procdump64.exe, 0 is used twice on purpose. PROCDUMP_PATH and PATH are literal and should not be localized. + + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.de.xlf b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.de.xlf index 90b9e239ac..ead98d0c5f 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.de.xlf +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.de.xlf @@ -83,6 +83,11 @@ Dieser Test kann, muss aber nicht unbedingt die Absturzursache sein. {0} wurde nicht gefunden. Stellen Sie sicher, dass die ausführbare Datei unter PATH verfügbar ist. Legen Sie alternativ die Umgebungsvariable PROCDUMP_PATH auf ein Verzeichnis fest, das {0} ausführbare Datei enthält. 0 is procDumpPath, a name of procdump executable such as procdump64.exe, 0 is used twice on purpose. PROCDUMP_PATH and PATH are literal and should not be localized. + + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.es.xlf b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.es.xlf index 7828594a88..6dc8150a31 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.es.xlf +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.es.xlf @@ -83,6 +83,11 @@ Esta prueba puede ser el origen del bloqueo o no serlo. no se encontró {0}. Cerciórese de que el ejecutable está disponible en PATH o establezca variable de entorno PROCDUMP_PATH en un directorio que contenga {0} ejecutable 0 is procDumpPath, a name of procdump executable such as procdump64.exe, 0 is used twice on purpose. PROCDUMP_PATH and PATH are literal and should not be localized. + + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.fr.xlf b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.fr.xlf index 5247da1247..eca5c90681 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.fr.xlf +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.fr.xlf @@ -83,6 +83,11 @@ Ce test est éventuellement à l'origine du plantage. {0} est introuvable, vérifiez que l’exécutable est disponible sur PATH. Vous pouvez également définir PROCDUMP_PATH variable d’environnement sur un répertoire qui contient l’exécutable {0} 0 is procDumpPath, a name of procdump executable such as procdump64.exe, 0 is used twice on purpose. PROCDUMP_PATH and PATH are literal and should not be localized. + + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.it.xlf b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.it.xlf index 5e86ffae6c..b5f0b15d38 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.it.xlf +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.it.xlf @@ -83,6 +83,11 @@ Il test potrebbe essere l'origine dell'arresto anomalo. {0} non è stato trovato, verificare che il file eseguibile sia disponibile in PATH. In alternativa, impostare la variabile di ambiente PROCDUMP_PATH su una directory che contiene {0} eseguibile 0 is procDumpPath, a name of procdump executable such as procdump64.exe, 0 is used twice on purpose. PROCDUMP_PATH and PATH are literal and should not be localized. + + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.ja.xlf b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.ja.xlf index 9bd59801b9..403a6d18ef 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.ja.xlf +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.ja.xlf @@ -83,6 +83,11 @@ This test may, or may not be the source of the crash. {0} が見つかりませんでした。PATH で実行可能ファイルを使用できることを確認してください。または、PROCDUMP_PATH 環境変数を、実行可能ファイル {0} が含まれるディレクトリに設定してください 0 is procDumpPath, a name of procdump executable such as procdump64.exe, 0 is used twice on purpose. PROCDUMP_PATH and PATH are literal and should not be localized. + + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.ko.xlf b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.ko.xlf index 509603766f..83a7d8c508 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.ko.xlf +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.ko.xlf @@ -83,6 +83,11 @@ This test may, or may not be the source of the crash. {0}을(를) 찾을 수 없습니다. 실행 파일을 PATH에서 사용할 수 있는지 확인합니다. 또는 PROCDUMP_PATH 환경 변수를 {0} 실행 파일이 포함된 디렉터리로 설정하세요. 0 is procDumpPath, a name of procdump executable such as procdump64.exe, 0 is used twice on purpose. PROCDUMP_PATH and PATH are literal and should not be localized. + + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.pl.xlf b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.pl.xlf index 456344a892..99808fc4cc 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.pl.xlf +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.pl.xlf @@ -83,6 +83,11 @@ Ten test może, ale nie musi być źródłem awarii. Nie można odnaleźć pliku wykonywalnego {0}. Upewnij się, że plik wykonywalny jest dostępny w ścieżce PATH. Możesz też ustawić zmienną środowiskową PROCDUMP_PATH na katalog zawierający plik wykonywalny {0}. 0 is procDumpPath, a name of procdump executable such as procdump64.exe, 0 is used twice on purpose. PROCDUMP_PATH and PATH are literal and should not be localized. + + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.pt-BR.xlf b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.pt-BR.xlf index cb27c78bb6..d1433ae80b 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.pt-BR.xlf +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.pt-BR.xlf @@ -83,6 +83,11 @@ Esse teste pode ser a origem da falha ou não. {0} não pôde ser encontrado, verifique se o executável está disponível no CAMINHO. Alternativamente, defina a variável de ambiente PROCDUMP_PATH para um diretório que contenha o executável {0} 0 is procDumpPath, a name of procdump executable such as procdump64.exe, 0 is used twice on purpose. PROCDUMP_PATH and PATH are literal and should not be localized. + + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.ru.xlf b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.ru.xlf index 274e899454..da787a7fcc 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.ru.xlf +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.ru.xlf @@ -83,6 +83,11 @@ This test may, or may not be the source of the crash. Не удалось найти {0}. Убедитесь, что исполняемый файл доступен в PATH. Также можно настроить переменную среды PROCDUMP_PATH, указав каталог, содержащий исполняемый файл {0}. 0 is procDumpPath, a name of procdump executable such as procdump64.exe, 0 is used twice on purpose. PROCDUMP_PATH and PATH are literal and should not be localized. + + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.tr.xlf b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.tr.xlf index d609155af3..c741a99f71 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.tr.xlf +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.tr.xlf @@ -83,6 +83,11 @@ Bu test, kilitlenmenin kaynağı olmayabilir veya olmayabilir. {0} bulunamadı, lütfen yürütülebilir dosyanın PATH üzerinde kullanılabilir olduğundan emin olun veya PROCDUMP_PATH ortam değişkenini {0} yürütülebilir dosyasını içeren bir dizine ayarlayın. 0 is procDumpPath, a name of procdump executable such as procdump64.exe, 0 is used twice on purpose. PROCDUMP_PATH and PATH are literal and should not be localized. + + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.xlf b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.xlf index 32084de5ff..fe7ba01906 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.xlf +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.xlf @@ -80,6 +80,11 @@ This test may, or may not be the source of the crash. {0} could not be found, please make sure that the executable is available on PATH, or set PROCDUMP_PATH environment variable to a directory that contains {0} executable. 0 is procDumpPath, a name of procdump executable such as procdump64.exe, 0 is used twice on purpose. PROCDUMP_PATH and PATH are literal and should not be localized. + + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.zh-Hans.xlf b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.zh-Hans.xlf index 8e7aeb9331..6f8d59f37a 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.zh-Hans.xlf +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.zh-Hans.xlf @@ -83,6 +83,11 @@ This test may, or may not be the source of the crash. 找不到 {0},请确保可执行文件在路径上可用,或者将 PROCDUMP_PATH 环境变量设置为包含 {0} 可执行文件的目录。 0 is procDumpPath, a name of procdump executable such as procdump64.exe, 0 is used twice on purpose. PROCDUMP_PATH and PATH are literal and should not be localized. + + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.zh-Hant.xlf b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.zh-Hant.xlf index 48187262da..75b719beaf 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.zh-Hant.xlf +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.zh-Hant.xlf @@ -83,6 +83,11 @@ This test may, or may not be the source of the crash. 找不到{0},請確定可執行檔可在 PATH 上使用。或將 PROCDUMP_PATH 環境變數設定為包含 {0} 可執行檔的目錄。 0 is procDumpPath, a name of procdump executable such as procdump64.exe, 0 is used twice on purpose. PROCDUMP_PATH and PATH are literal and should not be localized. + + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + Invalid 'DumpDirectoryPath' for postmortem debugger monitor + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.ObjectModel/Constants.cs b/src/Microsoft.TestPlatform.ObjectModel/Constants.cs index f8ec6bb106..297e772a51 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Constants.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/Constants.cs @@ -35,6 +35,12 @@ public static class Constants /// public const string BlameCollectDumpKey = "CollectDump"; + /// + /// Name of collect dump option for blame. + /// The internal visibility is because the new feature is not publicly exposed yet and so we can retire it. + /// + internal const string BlameCollectMonitorPostMortemDebuggerKey = "MonitorPostmortemDebugger"; + /// /// Name of collect dump option for blame. /// diff --git a/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Shipped.txt index 5d69e70774..e56a11ad0d 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Shipped.txt +++ b/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Shipped.txt @@ -966,3 +966,5 @@ virtual Microsoft.VisualStudio.TestPlatform.ObjectModel.TestObject.ProtectedSetP ~static Microsoft.VisualStudio.TestPlatform.ObjectModel.Resources.CommonResources.MalformedRunSettingsFile.get -> string ~static Microsoft.VisualStudio.TestPlatform.ObjectModel.Resources.CommonResources.NoMatchingSourcesFound.get -> string ~static Microsoft.VisualStudio.TestPlatform.ObjectModel.Resources.CommonResources.SourceIncompatible.get -> string +Microsoft.VisualStudio.TestPlatform.ObjectModel.DirectoryBasedTestDiscovererAttribute +Microsoft.VisualStudio.TestPlatform.ObjectModel.DirectoryBasedTestDiscovererAttribute.DirectoryBasedTestDiscovererAttribute() -> void diff --git a/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Unshipped.txt index 053259ecf8..ab058de62d 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Unshipped.txt @@ -1,3 +1 @@ #nullable enable -Microsoft.VisualStudio.TestPlatform.ObjectModel.DirectoryBasedTestDiscovererAttribute -Microsoft.VisualStudio.TestPlatform.ObjectModel.DirectoryBasedTestDiscovererAttribute.DirectoryBasedTestDiscovererAttribute() -> void diff --git a/src/vstest.console/Processors/AeDebuggerArgumentProcessor.cs b/src/vstest.console/Processors/AeDebuggerArgumentProcessor.cs new file mode 100644 index 0000000000..c76cd78234 --- /dev/null +++ b/src/vstest.console/Processors/AeDebuggerArgumentProcessor.cs @@ -0,0 +1,226 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Linq; + +using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors.Utilities; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; +using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; +using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; +using Microsoft.VisualStudio.TestPlatform.Utilities; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; + +using CommandLineResources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources.Resources; + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; + +internal class AeDebuggerArgumentProcessor : IArgumentProcessor +{ + public const string CommandName = "/AeDebugger"; + private Lazy? _metadata; + private Lazy? _executor; + + + public Lazy? Executor + { + get => _executor ??= new Lazy(() => + new AeDebuggerArgumentExecutor(new PlatformEnvironment(), new FileHelper(), new ProcessHelper(), ConsoleOutput.Instance)); + + set => _executor = value; + } + + public Lazy Metadata + => _metadata ??= new Lazy(() => new AeDebuggerArgumentProcessorCapabilities()); +} + +internal class AeDebuggerArgumentProcessorCapabilities : BaseArgumentProcessorCapabilities +{ + public override string CommandName => AeDebuggerArgumentProcessor.CommandName; + + public override bool AllowMultiple => false; + + public override bool IsAction => true; + + public override ArgumentProcessorPriority Priority => ArgumentProcessorPriority.Normal; + + // This feature is for internal usage at the moment, we can advertise in future when we'll have + // good feedback on the usage. + public override string? HelpContentResourceName => null; + + public override HelpContentPriority HelpPriority => HelpContentPriority.EnableDiagArgumentProcessorHelpPriority; +} + +internal class AeDebuggerArgumentExecutor : IArgumentExecutor +{ + private const int ProcDumpTimeoutSeconds = 10; + private const string InstallCommandArgumentName = "Install"; + private const string UninstallCommandArgumentName = "Uninstall"; + + private readonly IEnvironment _environment; + private readonly IFileHelper _fileHelper; + private readonly IProcessHelper _processHelper; + private readonly IOutput _output; + private string? _argument; + private Dictionary? _collectDumpParameters; + + public AeDebuggerArgumentExecutor(IEnvironment environment, IFileHelper fileHelper, IProcessHelper processHelper, IOutput output) + { + _environment = environment ?? throw new ArgumentNullException(nameof(environment)); + _fileHelper = fileHelper ?? throw new ArgumentNullException(nameof(fileHelper)); + _processHelper = processHelper ?? throw new ArgumentNullException(nameof(processHelper)); + _output = output ?? throw new ArgumentNullException(nameof(output)); + } + + public void Initialize(string? argument) => _argument = argument; + + public ArgumentProcessorResult Execute() + { + string exceptionMessage = string.Format(CultureInfo.CurrentCulture, CommandLineResources.InvalidAeDebuggerArgument, _argument ?? ""); + if (StringUtils.IsNullOrEmpty(_argument)) + { + _output.Error(false, exceptionMessage); + return ArgumentProcessorResult.Fail; + } + + string[] aeDebuggerArgumentList = ArgumentProcessorUtilities.GetArgumentList(_argument, ArgumentProcessorUtilities.SemiColonArgumentSeparator, exceptionMessage); + _collectDumpParameters = ArgumentProcessorUtilities.GetArgumentParameters( + aeDebuggerArgumentList.Where(x => !x.Equals(InstallCommandArgumentName, StringComparison.OrdinalIgnoreCase) && + !x.Equals(UninstallCommandArgumentName, StringComparison.OrdinalIgnoreCase)), + ArgumentProcessorUtilities.EqualNameValueSeparator, exceptionMessage); + + if (aeDebuggerArgumentList.Contains(InstallCommandArgumentName, StringComparer.OrdinalIgnoreCase)) + { + return InstallUnistallPostmortemDebugger(true); + } + + if (aeDebuggerArgumentList.Contains(UninstallCommandArgumentName, StringComparer.OrdinalIgnoreCase)) + { + return InstallUnistallPostmortemDebugger(false); + } + + _output.Error(false, exceptionMessage); + return ArgumentProcessorResult.Fail; + } + + private ArgumentProcessorResult InstallUnistallPostmortemDebugger(bool install) + { + if (_environment.OperatingSystem != PlatformOperatingSystem.Windows) + { + _output.Error(false, string.Format(CultureInfo.CurrentCulture, CommandLineResources.PostmortemDebuggerNotSupportedForCurrentOS)); + return ArgumentProcessorResult.Fail; + } + + if (_collectDumpParameters is null) + { + _output.Error(false, string.Format(CultureInfo.CurrentCulture, CommandLineResources.ProcDumpToolDirectoryPathArgumenNotFound)); + return ArgumentProcessorResult.Fail; + } + + // Validate ProcDumpToolDirectoryPath + if (!TryGetDirectoryInfo(_collectDumpParameters, + "ProcDumpToolDirectoryPath", + CommandLineResources.ProcDumpToolDirectoryPathArgumenNotFound, + CommandLineResources.InvalidProcDumpToolDirectoryPath, + out DirectoryInfo? procDumpToolDirectoryPath)) + { + return ArgumentProcessorResult.Fail; + } + + // Looking for procdump*.exe + FileInfo procDumpFileName = new(Path.Combine(procDumpToolDirectoryPath.FullName, ProcDumpFileName())); + if (!_fileHelper.Exists(procDumpFileName.FullName)) + { + _output.Error(false, string.Format(CultureInfo.CurrentCulture, CommandLineResources.ProcDumpFileNameNotFound, procDumpFileName.FullName)); + return ArgumentProcessorResult.Fail; + } + + string procDumpInstallUnistallArgument = ""; + if (install) + { + // Validate ProcDumpDirectoryPath + if (!TryGetDirectoryInfo(_collectDumpParameters, + "DumpDirectoryPath", + CommandLineResources.ProcDumpDirectoryPathArgumenNotFound, + CommandLineResources.InvalidProcDumpDirectoryPath, + out DirectoryInfo? dumpDirectoryPath)) + { + return ArgumentProcessorResult.Fail; + } + + procDumpInstallUnistallArgument = dumpDirectoryPath.FullName; + } + + if (_processHelper.LaunchProcess(procDumpFileName.FullName, install ? "-ma -i" : "-u", procDumpInstallUnistallArgument, null, + (_, data) => + { + if (data is not null && !StringUtilities.IsNullOrWhiteSpace(data)) + { + _output.Error(false, data, null); + } + }, + null, + (_, data) => + { + if (data is not null && !StringUtilities.IsNullOrWhiteSpace(data)) + { + _output.Information(false, data, null); + } + }) + is Process process) + { + return !process.WaitForExit(TimeSpan.FromSeconds(ProcDumpTimeoutSeconds).Seconds) + ? ArgumentProcessorResult.Fail + : process.ExitCode == 0 ? ArgumentProcessorResult.Success : ArgumentProcessorResult.Fail; + } + + // We suppose a success if the object returned by the LaunchProcess is not a Process object. + return ArgumentProcessorResult.Success; + + string ProcDumpFileName() => + _processHelper.GetCurrentProcessArchitecture() switch + { + PlatformArchitecture.X86 => "procdump.exe", + PlatformArchitecture.ARM64 => "procdump64a.exe", + _ => "procdump64.exe", + }; + + bool TryGetDirectoryInfo(Dictionary collectDumpParameters, + string directoryArgumentName, + string invalidArgumentErrorMessage, + string invalidDirectoryErrorMessage, + [NotNullWhen(true)] out DirectoryInfo? directoryInfo) + { + directoryInfo = null; + + if (!collectDumpParameters.TryGetValue(directoryArgumentName, out string? directoryPath)) + { + _output.Error(false, string.Format(CultureInfo.CurrentCulture, invalidArgumentErrorMessage)); + return false; + } + + if (directoryPath is null) + { + _output.Error(false, string.Format(CultureInfo.CurrentCulture, invalidArgumentErrorMessage)); + return false; + } + + directoryInfo = new(directoryPath); + if (!_fileHelper.DirectoryExists(directoryInfo.FullName)) + { + _output.Error(false, string.Format(CultureInfo.CurrentCulture, invalidDirectoryErrorMessage, directoryInfo.FullName)); + directoryInfo = null; + return false; + } + + return true; + } + } + +} diff --git a/src/vstest.console/Processors/EnableBlameArgumentProcessor.cs b/src/vstest.console/Processors/EnableBlameArgumentProcessor.cs index 639cf95b8a..e088322a65 100644 --- a/src/vstest.console/Processors/EnableBlameArgumentProcessor.cs +++ b/src/vstest.console/Processors/EnableBlameArgumentProcessor.cs @@ -120,19 +120,21 @@ public void Initialize(string? argument) { var enableDump = false; var enableHangDump = false; + var monitorPostMortemDebugger = false; var exceptionMessage = string.Format(CultureInfo.CurrentCulture, CommandLineResources.InvalidBlameArgument, argument); Dictionary? collectDumpParameters = null; - if (!argument.IsNullOrWhiteSpace()) { // Get blame argument list. - var blameArgumentList = ArgumentProcessorUtilities.GetArgumentList(argument, ArgumentProcessorUtilities.SemiColonArgumentSeparator, exceptionMessage); + string[] blameArgumentList = ArgumentProcessorUtilities.GetArgumentList(argument, ArgumentProcessorUtilities.SemiColonArgumentSeparator, exceptionMessage); Func isDumpCollect = a => Constants.BlameCollectDumpKey.Equals(a, StringComparison.OrdinalIgnoreCase); Func isHangDumpCollect = a => Constants.BlameCollectHangDumpKey.Equals(a, StringComparison.OrdinalIgnoreCase); + Func isMonitorPostmortemDebugger = a => Constants.BlameCollectMonitorPostMortemDebuggerKey.Equals(a, StringComparison.OrdinalIgnoreCase); // Get collect dump key. var hasCollectDumpKey = blameArgumentList.Any(isDumpCollect); var hasCollectHangDumpKey = blameArgumentList.Any(isHangDumpCollect); + var hasMonitorPostmortemDebugger = blameArgumentList.Any(isMonitorPostmortemDebugger); // Check if dump should be enabled or not. enableDump = hasCollectDumpKey; @@ -140,38 +142,41 @@ public void Initialize(string? argument) // Check if dump should be enabled or not. enableHangDump = hasCollectHangDumpKey; - if (!enableDump && !enableHangDump) + // Check if we need to monitor the postmortem debugger folder + monitorPostMortemDebugger = hasMonitorPostmortemDebugger; + + if (!enableDump && !enableHangDump && !monitorPostMortemDebugger) { Output.Warning(false, string.Format(CultureInfo.CurrentCulture, CommandLineResources.BlameIncorrectOption, argument)); } else { // Get collect dump parameters. - var collectDumpParameterArgs = blameArgumentList.Where(a => !isDumpCollect(a) && !isHangDumpCollect(a)); + IEnumerable collectDumpParameterArgs = blameArgumentList + .Where(a => !isDumpCollect(a) && + !isHangDumpCollect(a) && + !isMonitorPostmortemDebugger(a)); + collectDumpParameters = ArgumentProcessorUtilities.GetArgumentParameters(collectDumpParameterArgs, ArgumentProcessorUtilities.EqualNameValueSeparator, exceptionMessage); } } // Initialize blame. - InitializeBlame(enableDump, enableHangDump, collectDumpParameters); + InitializeBlame(enableDump, enableHangDump, monitorPostMortemDebugger, collectDumpParameters); } /// /// Executes the argument processor. /// /// The . - public ArgumentProcessorResult Execute() - { - // Nothing to do since we updated the logger and data collector list in initialize - return ArgumentProcessorResult.Success; - } + public ArgumentProcessorResult Execute() => ArgumentProcessorResult.Success; /// /// Initialize blame. /// /// Enable dump. /// Blame parameters. - private void InitializeBlame(bool enableCrashDump, bool enableHangDump, Dictionary? collectDumpParameters) + private void InitializeBlame(bool enableCrashDump, bool enableHangDump, bool monitorPostMortemDebugger, Dictionary? collectDumpParameters) { // Add Blame Logger LoggerUtilities.AddLoggerToRunSettings(BlameFriendlyName, null, _runSettingsManager); @@ -193,8 +198,6 @@ private void InitializeBlame(bool enableCrashDump, bool enableHangDump, Dictiona // Get data collection run settings. Create if not present. var dataCollectionRunSettings = XmlRunSettingsUtilities.GetDataCollectionRunSettings(settings); dataCollectionRunSettings ??= new DataCollectionRunSettings(); - - // Create blame configuration element. var xmlDocument = new XmlDocument(); var outernode = xmlDocument.CreateElement("Configuration"); var node = xmlDocument.CreateElement("ResultsDirectory"); @@ -238,6 +241,21 @@ private void InitializeBlame(bool enableCrashDump, bool enableHangDump, Dictiona AddCollectHangDumpNode(hangDumpParameters, xmlDocument, outernode); } + // Check if have to monitor the post mortem debugger + if (monitorPostMortemDebugger) + { + if (collectDumpParameters is not null) + { + // We don't need to check if present or not if null we'll set empty dump directory path + collectDumpParameters.TryGetValue("DumpDirectoryPath", out string? directoryPath); + Dictionary monitorPostMortemDebuggerParameters = new() + { + { "DumpDirectoryPath", directoryPath ?? "" } + }; + AddMonitorPostMortemDebuggerNode(monitorPostMortemDebuggerParameters, xmlDocument, outernode); + } + } + // Add blame configuration element to blame collector. foreach (var item in dataCollectionRunSettings.DataCollectorSettingsList) { @@ -285,17 +303,7 @@ private void InitializeBlame(bool enableCrashDump, bool enableHangDump, Dictiona /// Outer node. private static void AddCollectDumpNode(Dictionary? parameters, XmlDocument xmlDocument, XmlElement outernode) { - var dumpNode = xmlDocument.CreateElement(Constants.BlameCollectDumpKey); - if (parameters != null && parameters.Count > 0) - { - foreach (KeyValuePair entry in parameters) - { - var attribute = xmlDocument.CreateAttribute(entry.Key); - attribute.Value = entry.Value; - dumpNode.Attributes.Append(attribute); - } - } - outernode.AppendChild(dumpNode); + AddNode(parameters, xmlDocument, outernode, Constants.BlameCollectDumpKey); } /// @@ -306,7 +314,29 @@ private static void AddCollectDumpNode(Dictionary? parameters, X /// Outer node. private static void AddCollectHangDumpNode(Dictionary parameters, XmlDocument xmlDocument, XmlElement outernode) { - var dumpNode = xmlDocument.CreateElement(Constants.CollectDumpOnTestSessionHang); + AddNode(parameters, xmlDocument, outernode, Constants.CollectDumpOnTestSessionHang); + } + + /// + /// Adds monitor postmortem debuggerNode de dump node in outer node. + /// + /// Parameters. + /// Xml document. + /// Outer node. + private static void AddMonitorPostMortemDebuggerNode(Dictionary parameters, XmlDocument xmlDocument, XmlElement outernode) + { + AddNode(parameters, xmlDocument, outernode, Constants.BlameCollectMonitorPostMortemDebuggerKey); + } + + /// + /// Adds node in outer node. + /// + /// Parameters. + /// Xml document. + /// Outer node. + private static void AddNode(Dictionary? parameters, XmlDocument xmlDocument, XmlElement outernode, string nodeName) + { + var dumpNode = xmlDocument.CreateElement(nodeName); if (parameters != null && parameters.Count > 0) { foreach (KeyValuePair entry in parameters) diff --git a/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs b/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs index 3b5d5085a6..f42c8170d8 100644 --- a/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs +++ b/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs @@ -218,6 +218,7 @@ public IEnumerable GetArgumentProcessorsToAlwaysExecute() new DisableAutoFakesArgumentProcessor(), new ResponseFileArgumentProcessor(), new EnableBlameArgumentProcessor(), + new AeDebuggerArgumentProcessor(), new UseVsixExtensionsArgumentProcessor(), new ListDiscoverersArgumentProcessor(), new ListExecutorsArgumentProcessor(), diff --git a/src/vstest.console/Processors/Utilities/ArgumentProcessorUtilities.cs b/src/vstest.console/Processors/Utilities/ArgumentProcessorUtilities.cs index b4fc798f69..efac4c6614 100644 --- a/src/vstest.console/Processors/Utilities/ArgumentProcessorUtilities.cs +++ b/src/vstest.console/Processors/Utilities/ArgumentProcessorUtilities.cs @@ -18,7 +18,7 @@ internal class ArgumentProcessorUtilities /// Argument separator. /// Exception Message. /// Argument list. - public static string[] GetArgumentList(string rawArgument, char[] argumentSeparator, string exceptionMessage) + public static string[] GetArgumentList(string? rawArgument, char[] argumentSeparator, string exceptionMessage) { var argumentList = rawArgument?.Split(argumentSeparator, StringSplitOptions.RemoveEmptyEntries); diff --git a/src/vstest.console/Resources/Resources.Designer.cs b/src/vstest.console/Resources/Resources.Designer.cs index 41abfa4f25..1b5aa9870b 100644 --- a/src/vstest.console/Resources/Resources.Designer.cs +++ b/src/vstest.console/Resources/Resources.Designer.cs @@ -533,6 +533,15 @@ internal static string EnvironmentVariableXIsOverriden { } } + /// + /// Looks up a localized string similar to Error hosting communication channel.. + /// + internal static string ErrorHostingCommunicationChannel { + get { + return ResourceManager.GetString("ErrorHostingCommunicationChannel", resourceCulture); + } + } + /// /// Looks up a localized string similar to Error Message:. /// @@ -705,6 +714,15 @@ internal static string InIsolationHelp { } } + /// + /// Looks up a localized string similar to AeDebugger argument '{0}' is not valid.. + /// + internal static string InvalidAeDebuggerArgument { + get { + return ResourceManager.GetString("InvalidAeDebuggerArgument", resourceCulture); + } + } + /// /// Looks up a localized string similar to The argument {0} is invalid. Please use the /help option to check the list of valid arguments.. /// @@ -831,6 +849,24 @@ internal static string InvalidPortArgument { } } + /// + /// Looks up a localized string similar to The directory specified is not valid: '{0}'. + /// + internal static string InvalidProcDumpDirectoryPath { + get { + return ResourceManager.GetString("InvalidProcDumpDirectoryPath", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The directory specified is not valid: '{0}'. + /// + internal static string InvalidProcDumpToolDirectoryPath { + get { + return ResourceManager.GetString("InvalidProcDumpToolDirectoryPath", resourceCulture); + } + } + /// /// Looks up a localized string similar to The path '{0}' specified in the 'ResultsDirectory' is invalid. Error: {1}. /// @@ -1214,6 +1250,42 @@ internal static string PortArgumentHelp { } } + /// + /// Looks up a localized string similar to Postmortem debugger is not supported in the current OS.. + /// + internal static string PostmortemDebuggerNotSupportedForCurrentOS { + get { + return ResourceManager.GetString("PostmortemDebuggerNotSupportedForCurrentOS", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to DumpDirectoryPath should be specified to install the post mortem debugger.. + /// + internal static string ProcDumpDirectoryPathArgumenNotFound { + get { + return ResourceManager.GetString("ProcDumpDirectoryPathArgumenNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Procdump file name not found: '{0}'. + /// + internal static string ProcDumpFileNameNotFound { + get { + return ResourceManager.GetString("ProcDumpFileNameNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger.. + /// + internal static string ProcDumpToolDirectoryPathArgumenNotFound { + get { + return ResourceManager.GetString("ProcDumpToolDirectoryPathArgumenNotFound", resourceCulture); + } + } + /// /// Looks up a localized string similar to Test run in progress. /// @@ -1843,14 +1915,5 @@ internal static string WarningEmulatedOnArm64 { return ResourceManager.GetString("WarningEmulatedOnArm64", resourceCulture); } } - - /// - /// Looks up a localized string similar to Error hosting the communication channel. For better performance, please consider using the native runner vstest.console.arm64.exe.. - /// - internal static string ErrorHostingCommunicationChannel { - get { - return ResourceManager.GetString("ErrorHostingCommunicationChannel", resourceCulture); - } - } } } diff --git a/src/vstest.console/Resources/Resources.resx b/src/vstest.console/Resources/Resources.resx index c055157808..f4638cc23c 100644 --- a/src/vstest.console/Resources/Resources.resx +++ b/src/vstest.console/Resources/Resources.resx @@ -748,6 +748,27 @@ Blame argument '{0}' is not valid. + + AeDebugger argument '{0}' is not valid. + + + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + + + DumpDirectoryPath should be specified to install the post mortem debugger. + + + Postmortem debugger is not supported in the current OS. + + + The directory specified is not valid: '{0}' + + + The directory specified is not valid: '{0}' + + + Procdump file name not found: '{0}' + Diag argument '{0}' is not valid. diff --git a/src/vstest.console/Resources/xlf/Resources.cs.xlf b/src/vstest.console/Resources/xlf/Resources.cs.xlf index 3a29f6d877..eee6429d49 100644 --- a/src/vstest.console/Resources/xlf/Resources.cs.xlf +++ b/src/vstest.console/Resources/xlf/Resources.cs.xlf @@ -1199,6 +1199,41 @@ Při hostování komunikačního kanálu došlo k chybě. + + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + + + + Postmortem debugger is not supported in the current OS. + Postmortem debugger is not supported in the current OS. + + + + The directory specified is not valid: '{0}' + The directory specified is not valid: '{0}' + + + + Procdump file name not found: '{0}' + Procdump file name not found: '{0}' + + + + DumpDirectoryPath should be specified to install the post mortem debugger. + ProcDumpDirectoryPath should be specified to install the post mortem debugger. + + + + The directory specified is not valid: '{0}' + The directory specified is not valid: '{0}' + + + + AeDebugger argument '{0}' is not valid. + Blame argument '{0}' is not valid. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.de.xlf b/src/vstest.console/Resources/xlf/Resources.de.xlf index 619aee7f2a..ca0ac6f52c 100644 --- a/src/vstest.console/Resources/xlf/Resources.de.xlf +++ b/src/vstest.console/Resources/xlf/Resources.de.xlf @@ -1199,6 +1199,41 @@ Fehler beim Hosten des Kommunikationskanals. + + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + + + + Postmortem debugger is not supported in the current OS. + Postmortem debugger is not supported in the current OS. + + + + The directory specified is not valid: '{0}' + The directory specified is not valid: '{0}' + + + + Procdump file name not found: '{0}' + Procdump file name not found: '{0}' + + + + DumpDirectoryPath should be specified to install the post mortem debugger. + ProcDumpDirectoryPath should be specified to install the post mortem debugger. + + + + The directory specified is not valid: '{0}' + The directory specified is not valid: '{0}' + + + + AeDebugger argument '{0}' is not valid. + Blame argument '{0}' is not valid. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.es.xlf b/src/vstest.console/Resources/xlf/Resources.es.xlf index 481113f8ca..4c114b1abd 100644 --- a/src/vstest.console/Resources/xlf/Resources.es.xlf +++ b/src/vstest.console/Resources/xlf/Resources.es.xlf @@ -1202,6 +1202,41 @@ Error al hospedar el canal de comunicación. + + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + + + + Postmortem debugger is not supported in the current OS. + Postmortem debugger is not supported in the current OS. + + + + The directory specified is not valid: '{0}' + The directory specified is not valid: '{0}' + + + + Procdump file name not found: '{0}' + Procdump file name not found: '{0}' + + + + DumpDirectoryPath should be specified to install the post mortem debugger. + ProcDumpDirectoryPath should be specified to install the post mortem debugger. + + + + The directory specified is not valid: '{0}' + The directory specified is not valid: '{0}' + + + + AeDebugger argument '{0}' is not valid. + Blame argument '{0}' is not valid. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.fr.xlf b/src/vstest.console/Resources/xlf/Resources.fr.xlf index cfc0be643b..02e44bf395 100644 --- a/src/vstest.console/Resources/xlf/Resources.fr.xlf +++ b/src/vstest.console/Resources/xlf/Resources.fr.xlf @@ -1199,6 +1199,41 @@ Comportements actuellement pris en charge : Erreur lors de l’hébergement du canal de communication + + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + + + + Postmortem debugger is not supported in the current OS. + Postmortem debugger is not supported in the current OS. + + + + The directory specified is not valid: '{0}' + The directory specified is not valid: '{0}' + + + + Procdump file name not found: '{0}' + Procdump file name not found: '{0}' + + + + DumpDirectoryPath should be specified to install the post mortem debugger. + ProcDumpDirectoryPath should be specified to install the post mortem debugger. + + + + The directory specified is not valid: '{0}' + The directory specified is not valid: '{0}' + + + + AeDebugger argument '{0}' is not valid. + Blame argument '{0}' is not valid. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.it.xlf b/src/vstest.console/Resources/xlf/Resources.it.xlf index 8b6f6f3f1a..2530afbecc 100644 --- a/src/vstest.console/Resources/xlf/Resources.it.xlf +++ b/src/vstest.console/Resources/xlf/Resources.it.xlf @@ -1199,6 +1199,41 @@ Errore nell'hosting del canale di comunicazione. + + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + + + + Postmortem debugger is not supported in the current OS. + Postmortem debugger is not supported in the current OS. + + + + The directory specified is not valid: '{0}' + The directory specified is not valid: '{0}' + + + + Procdump file name not found: '{0}' + Procdump file name not found: '{0}' + + + + DumpDirectoryPath should be specified to install the post mortem debugger. + ProcDumpDirectoryPath should be specified to install the post mortem debugger. + + + + The directory specified is not valid: '{0}' + The directory specified is not valid: '{0}' + + + + AeDebugger argument '{0}' is not valid. + Blame argument '{0}' is not valid. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.ja.xlf b/src/vstest.console/Resources/xlf/Resources.ja.xlf index 2b0f883c42..4d5748705f 100644 --- a/src/vstest.console/Resources/xlf/Resources.ja.xlf +++ b/src/vstest.console/Resources/xlf/Resources.ja.xlf @@ -1199,6 +1199,41 @@ 通信チャネルをホスト中にエラーが発生しました。 + + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + + + + Postmortem debugger is not supported in the current OS. + Postmortem debugger is not supported in the current OS. + + + + The directory specified is not valid: '{0}' + The directory specified is not valid: '{0}' + + + + Procdump file name not found: '{0}' + Procdump file name not found: '{0}' + + + + DumpDirectoryPath should be specified to install the post mortem debugger. + ProcDumpDirectoryPath should be specified to install the post mortem debugger. + + + + The directory specified is not valid: '{0}' + The directory specified is not valid: '{0}' + + + + AeDebugger argument '{0}' is not valid. + Blame argument '{0}' is not valid. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.ko.xlf b/src/vstest.console/Resources/xlf/Resources.ko.xlf index f33587e591..97056ba204 100644 --- a/src/vstest.console/Resources/xlf/Resources.ko.xlf +++ b/src/vstest.console/Resources/xlf/Resources.ko.xlf @@ -1199,6 +1199,41 @@ 통신 채널을 호스트하는 동안 오류가 발생했습니다. + + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + + + + Postmortem debugger is not supported in the current OS. + Postmortem debugger is not supported in the current OS. + + + + The directory specified is not valid: '{0}' + The directory specified is not valid: '{0}' + + + + Procdump file name not found: '{0}' + Procdump file name not found: '{0}' + + + + DumpDirectoryPath should be specified to install the post mortem debugger. + ProcDumpDirectoryPath should be specified to install the post mortem debugger. + + + + The directory specified is not valid: '{0}' + The directory specified is not valid: '{0}' + + + + AeDebugger argument '{0}' is not valid. + Blame argument '{0}' is not valid. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.pl.xlf b/src/vstest.console/Resources/xlf/Resources.pl.xlf index d17baffb64..71035aa5f3 100644 --- a/src/vstest.console/Resources/xlf/Resources.pl.xlf +++ b/src/vstest.console/Resources/xlf/Resources.pl.xlf @@ -1199,6 +1199,41 @@ Błąd podczas hostowania kanału komunikacji. + + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + + + + Postmortem debugger is not supported in the current OS. + Postmortem debugger is not supported in the current OS. + + + + The directory specified is not valid: '{0}' + The directory specified is not valid: '{0}' + + + + Procdump file name not found: '{0}' + Procdump file name not found: '{0}' + + + + DumpDirectoryPath should be specified to install the post mortem debugger. + ProcDumpDirectoryPath should be specified to install the post mortem debugger. + + + + The directory specified is not valid: '{0}' + The directory specified is not valid: '{0}' + + + + AeDebugger argument '{0}' is not valid. + Blame argument '{0}' is not valid. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.pt-BR.xlf b/src/vstest.console/Resources/xlf/Resources.pt-BR.xlf index 10e3f65b71..562c97c710 100644 --- a/src/vstest.console/Resources/xlf/Resources.pt-BR.xlf +++ b/src/vstest.console/Resources/xlf/Resources.pt-BR.xlf @@ -1199,6 +1199,41 @@ Altere o prefixo de nível de diagnóstico do agente de console, como mostrado a Erro ao hospedar o canal de comunicação. + + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + + + + Postmortem debugger is not supported in the current OS. + Postmortem debugger is not supported in the current OS. + + + + The directory specified is not valid: '{0}' + The directory specified is not valid: '{0}' + + + + Procdump file name not found: '{0}' + Procdump file name not found: '{0}' + + + + DumpDirectoryPath should be specified to install the post mortem debugger. + ProcDumpDirectoryPath should be specified to install the post mortem debugger. + + + + The directory specified is not valid: '{0}' + The directory specified is not valid: '{0}' + + + + AeDebugger argument '{0}' is not valid. + Blame argument '{0}' is not valid. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.ru.xlf b/src/vstest.console/Resources/xlf/Resources.ru.xlf index b1d98d76c3..f6987572bc 100644 --- a/src/vstest.console/Resources/xlf/Resources.ru.xlf +++ b/src/vstest.console/Resources/xlf/Resources.ru.xlf @@ -1199,6 +1199,41 @@ Ошибка при размещении коммуникационного канала. + + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + + + + Postmortem debugger is not supported in the current OS. + Postmortem debugger is not supported in the current OS. + + + + The directory specified is not valid: '{0}' + The directory specified is not valid: '{0}' + + + + Procdump file name not found: '{0}' + Procdump file name not found: '{0}' + + + + DumpDirectoryPath should be specified to install the post mortem debugger. + ProcDumpDirectoryPath should be specified to install the post mortem debugger. + + + + The directory specified is not valid: '{0}' + The directory specified is not valid: '{0}' + + + + AeDebugger argument '{0}' is not valid. + Blame argument '{0}' is not valid. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.tr.xlf b/src/vstest.console/Resources/xlf/Resources.tr.xlf index 0497e92fb1..f26d1dd4a9 100644 --- a/src/vstest.console/Resources/xlf/Resources.tr.xlf +++ b/src/vstest.console/Resources/xlf/Resources.tr.xlf @@ -1199,6 +1199,41 @@ Günlükler için izleme düzeyini aşağıda gösterildiği gibi değiştirin İletişim kanalını barındırma hatası. + + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + + + + Postmortem debugger is not supported in the current OS. + Postmortem debugger is not supported in the current OS. + + + + The directory specified is not valid: '{0}' + The directory specified is not valid: '{0}' + + + + Procdump file name not found: '{0}' + Procdump file name not found: '{0}' + + + + DumpDirectoryPath should be specified to install the post mortem debugger. + ProcDumpDirectoryPath should be specified to install the post mortem debugger. + + + + The directory specified is not valid: '{0}' + The directory specified is not valid: '{0}' + + + + AeDebugger argument '{0}' is not valid. + Blame argument '{0}' is not valid. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.xlf b/src/vstest.console/Resources/xlf/Resources.xlf index 02e3b2e4ac..e3c822c1b0 100644 --- a/src/vstest.console/Resources/xlf/Resources.xlf +++ b/src/vstest.console/Resources/xlf/Resources.xlf @@ -993,6 +993,41 @@ Format : TestRunParameters.Parameter(name="<name>", value="<value>") Error hosting communication channel. + + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + + + + Postmortem debugger is not supported in the current OS. + Postmortem debugger is not supported in the current OS. + + + + The directory specified is not valid: '{0}' + The directory specified is not valid: '{0}' + + + + Procdump file name not found: '{0}' + Procdump file name not found: '{0}' + + + + DumpDirectoryPath should be specified to install the post mortem debugger. + ProcDumpDirectoryPath should be specified to install the post mortem debugger. + + + + The directory specified is not valid: '{0}' + The directory specified is not valid: '{0}' + + + + AeDebugger argument '{0}' is not valid. + Blame argument '{0}' is not valid. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.zh-Hans.xlf b/src/vstest.console/Resources/xlf/Resources.zh-Hans.xlf index 7e1574635b..7daa5e7209 100644 --- a/src/vstest.console/Resources/xlf/Resources.zh-Hans.xlf +++ b/src/vstest.console/Resources/xlf/Resources.zh-Hans.xlf @@ -1199,6 +1199,41 @@ 托管信道时出错。 + + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + + + + Postmortem debugger is not supported in the current OS. + Postmortem debugger is not supported in the current OS. + + + + The directory specified is not valid: '{0}' + The directory specified is not valid: '{0}' + + + + Procdump file name not found: '{0}' + Procdump file name not found: '{0}' + + + + DumpDirectoryPath should be specified to install the post mortem debugger. + ProcDumpDirectoryPath should be specified to install the post mortem debugger. + + + + The directory specified is not valid: '{0}' + The directory specified is not valid: '{0}' + + + + AeDebugger argument '{0}' is not valid. + Blame argument '{0}' is not valid. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.zh-Hant.xlf b/src/vstest.console/Resources/xlf/Resources.zh-Hant.xlf index 305e436f08..76ab40c8f1 100644 --- a/src/vstest.console/Resources/xlf/Resources.zh-Hant.xlf +++ b/src/vstest.console/Resources/xlf/Resources.zh-Hant.xlf @@ -1199,6 +1199,41 @@ 裝載通訊通道時發生錯誤。 + + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + ProcDumpToolDirectoryPath should be specified to install or unistall the post mortem debugger. + + + + Postmortem debugger is not supported in the current OS. + Postmortem debugger is not supported in the current OS. + + + + The directory specified is not valid: '{0}' + The directory specified is not valid: '{0}' + + + + Procdump file name not found: '{0}' + Procdump file name not found: '{0}' + + + + DumpDirectoryPath should be specified to install the post mortem debugger. + ProcDumpDirectoryPath should be specified to install the post mortem debugger. + + + + The directory specified is not valid: '{0}' + The directory specified is not valid: '{0}' + + + + AeDebugger argument '{0}' is not valid. + Blame argument '{0}' is not valid. + + \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.AcceptanceTests/BlameDataCollectorTests.cs b/test/Microsoft.TestPlatform.AcceptanceTests/BlameDataCollectorTests.cs index 60b31fe970..9233342f2f 100644 --- a/test/Microsoft.TestPlatform.AcceptanceTests/BlameDataCollectorTests.cs +++ b/test/Microsoft.TestPlatform.AcceptanceTests/BlameDataCollectorTests.cs @@ -251,6 +251,44 @@ public void HangDumpChildProcesses(RunnerInfo runnerInfo) ValidateDump(2); } + [TestMethod] + [TestCategory("Windows-Review")] + [NetFullTargetFrameworkDataSource] + [NetCoreTargetFrameworkDataSource] + public void BlameDataCollectorAeDebuggerShouldCollectDump(RunnerInfo runnerInfo) + { + SetTestEnvironment(_testEnvironment, runnerInfo); + + // Install AeDebugger + string dumpPath = Path.Combine(TempDirectory.Path, "Dumps"); + Directory.CreateDirectory(dumpPath); + + ExecuteVsTestConsole($"/AeDebugger:Install;ProcDumpToolDirectoryPath={_procDumpPath};DumpDirectoryPath={dumpPath}", + out string standardTestOutput, + out string standardErrorTestOutput, + out int _); + Assert.IsTrue(standardErrorTestOutput.Trim().Length == 0); + + // Run test under postmortem monitoring + var assemblyPaths = GetAssetFullPath("BlameUnitTestProject.dll"); + var arguments = PrepareArguments(assemblyPaths, GetTestAdapterPath(), string.Empty, FrameworkArgValue, runnerInfo.InIsolationValue); + arguments = string.Concat(arguments, $" /Blame:MonitorPostmortemDebugger;DumpDirectoryPath={dumpPath}"); + arguments = string.Concat(arguments, $" /ResultsDirectory:{TempDirectory.Path}"); + InvokeVsTest(arguments); + + // Uninstall AeDebugger + ExecuteVsTestConsole($"/AeDebugger:Uninstall;ProcDumpToolDirectoryPath={_procDumpPath}", + out standardTestOutput, + out standardErrorTestOutput, + out int _); + Assert.IsTrue(standardErrorTestOutput.Trim().Length == 0); + + // We cannot be precise here procdump is at machine level so we can have more than one dump and not only the one for our test + // We look for "at least" one dump file, is the best we can do without locking all tests. + Assert.IsTrue(Directory.GetFiles(TempDirectory.Path, "*.dmp", SearchOption.AllDirectories) + .Where(x => Path.GetFileNameWithoutExtension(x).StartsWith("testhost")).Count() > 0); + } + private void ValidateDump(int expectedDumpCount = 1) { var attachments = StdOutWithWhiteSpace.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) diff --git a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/BlameCollectorTests.cs b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/BlameCollectorTests.cs index 9c702cfec1..3e7b6d8003 100644 --- a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/BlameCollectorTests.cs +++ b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/BlameCollectorTests.cs @@ -11,6 +11,7 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; +using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -33,6 +34,7 @@ public class BlameCollectorTests private readonly Mock _mockDataCollectionSink; private readonly Mock _mockBlameReaderWriter; private readonly Mock _mockProcessDumpUtility; + private readonly Mock _mockProcessHelper; private readonly Mock _mockInactivityTimer; private readonly Mock _mockFileHelper; private readonly XmlElement? _configurationElement; @@ -51,11 +53,13 @@ public BlameCollectorTests() _mockProcessDumpUtility = new Mock(); _mockInactivityTimer = new Mock(); _mockFileHelper = new Mock(); + _mockProcessHelper = new Mock(); _blameDataCollector = new TestableBlameCollector( _mockBlameReaderWriter.Object, _mockProcessDumpUtility.Object, _mockInactivityTimer.Object, - _mockFileHelper.Object); + _mockFileHelper.Object, + _mockProcessHelper.Object); // Initializing members TestCase testcase = new() { Id = Guid.NewGuid() }; @@ -162,7 +166,8 @@ public void InitializeWithDumpForHangShouldCaptureADumpOnTimeout() _mockBlameReaderWriter.Object, _mockProcessDumpUtility.Object, null, - _mockFileHelper.Object); + _mockFileHelper.Object, + _mockProcessHelper.Object); var dumpFile = "abc_hang.dmp"; var hangBasedDumpcollected = new ManualResetEventSlim(); @@ -197,7 +202,8 @@ public void InitializeWithDumpForHangShouldCaptureKillTestHostOnTimeoutEvenIfGet _mockBlameReaderWriter.Object, _mockProcessDumpUtility.Object, null, - _mockFileHelper.Object); + _mockFileHelper.Object, + _mockProcessHelper.Object); var hangBasedDumpcollected = new ManualResetEventSlim(); @@ -229,7 +235,8 @@ public void InitializeWithDumpForHangShouldCaptureKillTestHostOnTimeoutEvenIfAtt _mockBlameReaderWriter.Object, _mockProcessDumpUtility.Object, null, - _mockFileHelper.Object); + _mockFileHelper.Object, + _mockProcessHelper.Object); var dumpFile = "abc_hang.dmp"; var hangBasedDumpcollected = new ManualResetEventSlim(); @@ -755,8 +762,8 @@ internal class TestableBlameCollector : BlameCollector /// MockFileHelper instance. /// internal TestableBlameCollector(IBlameReaderWriter blameReaderWriter, IProcessDumpUtility processDumpUtility, IInactivityTimer? inactivityTimer, - IFileHelper mockFileHelper) - : base(blameReaderWriter, processDumpUtility, inactivityTimer, mockFileHelper) + IFileHelper mockFileHelper, IProcessHelper mockProcessHelper) + : base(blameReaderWriter, processDumpUtility, inactivityTimer, mockFileHelper, mockProcessHelper) { } } diff --git a/test/vstest.console.UnitTests/Processors/AeDebuggerArgumentProcessorTest.cs b/test/vstest.console.UnitTests/Processors/AeDebuggerArgumentProcessorTest.cs new file mode 100644 index 0000000000..41daddbd58 --- /dev/null +++ b/test/vstest.console.UnitTests/Processors/AeDebuggerArgumentProcessorTest.cs @@ -0,0 +1,131 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Globalization; + +using Microsoft.VisualStudio.TestPlatform.CommandLine; +using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; +using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; +using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; +using Microsoft.VisualStudio.TestPlatform.Utilities; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Moq; + +namespace vstest.console.UnitTests.Processors; + +[TestClass] +[TestCategory("Windows-Review")] +public class AeDebuggerArgumentProcessorTest +{ + private readonly Mock _environment = new(); + private readonly Mock _fileHelper = new(); + private readonly Mock _processHelper = new(); + private readonly Mock _output = new(); + private readonly AeDebuggerArgumentExecutor _executor; + + public AeDebuggerArgumentProcessorTest() + { + _executor = new AeDebuggerArgumentExecutor(_environment.Object, _fileHelper.Object, _processHelper.Object, _output.Object); + } + + [TestMethod] + public void AeDebuggerArgumentProcessorCommandName() + { + Assert.AreEqual("/AeDebugger", AeDebuggerArgumentProcessor.CommandName); + } + + [TestMethod] + public void AeDebuggerArgumentProcessorCapabilities() + { + AeDebuggerArgumentProcessorCapabilities aeDebuggerArgumentProcessor = new(); + Assert.IsNull(aeDebuggerArgumentProcessor.HelpContentResourceName); + Assert.IsTrue(aeDebuggerArgumentProcessor.IsAction); + Assert.AreEqual(ArgumentProcessorPriority.Normal, aeDebuggerArgumentProcessor.Priority); + } + + [TestMethod] + public void AeDebuggerArgumentProcessorReturnsCorrectTypes() + { + AeDebuggerArgumentProcessor aeDebuggerArgumentProcessor = new(); + Assert.IsInstanceOfType(aeDebuggerArgumentProcessor.Executor!.Value, typeof(AeDebuggerArgumentExecutor)); + Assert.IsInstanceOfType(aeDebuggerArgumentProcessor.Metadata!.Value, typeof(AeDebuggerArgumentProcessorCapabilities)); + } + + [TestMethod] + public void AeDebuggerArgumentExecutor_InvalidCtor() + { + Assert.ThrowsException(() => new AeDebuggerArgumentExecutor(_environment.Object, _fileHelper.Object, _processHelper.Object, null!)); + Assert.ThrowsException(() => new AeDebuggerArgumentExecutor(_environment.Object, _fileHelper.Object, null!, _output.Object)); + Assert.ThrowsException(() => new AeDebuggerArgumentExecutor(_environment.Object, null!, _processHelper.Object, _output.Object)); + Assert.ThrowsException(() => new AeDebuggerArgumentExecutor(null!, _fileHelper.Object, _processHelper.Object, _output.Object)); + } + + [TestMethod] + public void AeDebuggerArgumentExecutor_NullArgument() + { + _executor.Initialize(null); + Assert.AreEqual(ArgumentProcessorResult.Fail, _executor.Execute()); + _output.Verify(x => x.WriteLine(It.IsAny(), OutputLevel.Error), Times.Once()); + } + + [TestMethod] + [DataRow("Instal;ProcDumpToolDirectoryPath=c:\\ProcDumpToolDirectoryPath;DumpDirectoryPath=c:\\DumpDirectoryPath")] + [DataRow("Uninstal;ProcDumpToolDirectoryPath=c:\\ProcDumpToolDirectoryPath;DumpDirectoryPath=c:\\DumpDirectoryPath")] + public void AeDebuggerArgumentExecutor_WrongInstallUnistallCommand(string wrongCommand) + { + _executor.Initialize(wrongCommand); + Assert.ThrowsException(() => _executor.Execute()); + } + + [TestMethod] + public void AeDebuggerArgumentExecutor_NonWindowsOS() + { + _environment.Setup(x => x.OperatingSystem).Returns(PlatformOperatingSystem.Unix); + _executor.Initialize("Install;ProcDumpToolDirectoryPath=c:\\ProcDumpToolDirectoryPath;DumpDirectoryPath=c:\\DumpDirectoryPath"); + Assert.AreEqual(ArgumentProcessorResult.Fail, _executor.Execute()); + } + + [TestMethod] + [DataRow("Install;{0};DumpDirectoryPath=c:\\DumpDirectoryPath", null)] + [DataRow("Install;{0};DumpDirectoryPath=c:\\DumpDirectoryPath", "ProcDumpToolDirectoryPath=c:\\ProcDumpToolDirectoryPat")] + [DataRow("Install;{0};ProcDumpToolDirectoryPath=c:\\ProcDumpToolDirectoryPath", null)] + [DataRow("Install;{0};ProcDumpToolDirectoryPath=c:\\ProcDumpToolDirectoryPath", "DumpDirectoryPath=c:\\DumpDirectoryPat")] + + public void AeDebuggerArgumentExecutor_WrongDirectoryPaths(string command, string directoryPath) + { + _fileHelper.Setup(x => x.DirectoryExists(It.IsAny())) + .Returns((string path) => directoryPath is null || !directoryPath.EndsWith(path)); + _fileHelper.Setup(x => x.Exists(It.IsAny())) + .Returns((string path) => path.EndsWith("procdump.exe")); + _executor.Initialize(string.Format(CultureInfo.InvariantCulture, command, directoryPath)); + Assert.AreEqual(ArgumentProcessorResult.Fail, _executor.Execute()); + } + + [TestMethod] + [DataRow("Install;ProcDumpToolDirectoryPath=c:\\ProcDumpToolDirectoryPath;DumpDirectoryPath=c:\\DumpDirectoryPath", true)] + [DataRow("Uninstall;ProcDumpToolDirectoryPath=c:\\ProcDumpToolDirectoryPath;DumpDirectoryPath=c:\\DumpDirectoryPath", false)] + public void AeDebuggerArgumentExecutor_ProcdumpArgument(string command, bool install) + { + _fileHelper.Setup(x => x.DirectoryExists(It.IsAny())).Returns(true); + _fileHelper.Setup(x => x.Exists(It.IsAny())).Returns(true); + _processHelper.Setup(x => x.LaunchProcess( + It.IsAny(), + It.IsAny(), + It.IsAny(), + null, + It.IsAny>(), + It.IsAny>(), + It.IsAny>())) + .Returns((string processPath, string? arguments, string? workingDirectory, IDictionary? envVariables, Action? errorCallback, Action? exitCallBack, Action? outputCallBack) => + { + Assert.IsTrue(install ? arguments == "-ma -i" : arguments == "-u"); + return new object(); + }); + _executor.Initialize(command); + Assert.AreEqual(ArgumentProcessorResult.Success, _executor.Execute()); + } +} diff --git a/test/vstest.console.UnitTests/Processors/EnableBlameArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/EnableBlameArgumentProcessorTests.cs index 42bbd0996f..2a09e0145b 100644 --- a/test/vstest.console.UnitTests/Processors/EnableBlameArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/EnableBlameArgumentProcessorTests.cs @@ -418,6 +418,88 @@ public void InitializeShouldCreateEntryForBlameAlongWithCollectHangDumpParameter _settingsProvider.ActiveRunSettings.SettingsXml); } + [TestMethod] + [TestCategory("Windows-Review")] + public void InitializeMonitorPostmortemDebuggerShouldGenerateCorrectConfiguration() + { + var runsettingsString = string.Format(CultureInfo.CurrentCulture, _defaultRunSettings, ""); + var runsettings = new RunSettings(); + runsettings.LoadSettingsXml(_defaultRunSettings); + _settingsProvider.SetActiveRunSettings(runsettings); + + _mockEnvronment.Setup(x => x.OperatingSystem) + .Returns(PlatformOperatingSystem.Windows); + _mockEnvronment.Setup(x => x.Architecture) + .Returns(PlatformArchitecture.X64); + + _executor.Initialize("MonitorPostmortemDebugger;DumpDirectoryPath=c:\\DumpDirectoryPath"); + Assert.IsNotNull(_settingsProvider.ActiveRunSettings); + Assert.AreEqual(string.Join(Environment.NewLine, + "", + "", + " ", + " ", + " ", + " ", + " C:\\dir\\TestResults", + " ", + " ", + " ", + " ", + " ", + " ", + " C:\\dir\\TestResults", + " ", + " ", + " ", + " ", + " ", + " ", + ""), + _settingsProvider.ActiveRunSettings.SettingsXml); + } + + [TestMethod] + [TestCategory("Windows-Review")] + public void InitializeMonitorPostmortemDebuggerShouldGenerateCorrectConfigurationAlsoIfIncomplete() + { + var runsettingsString = string.Format(CultureInfo.CurrentCulture, _defaultRunSettings, ""); + var runsettings = new RunSettings(); + runsettings.LoadSettingsXml(_defaultRunSettings); + _settingsProvider.SetActiveRunSettings(runsettings); + + _mockEnvronment.Setup(x => x.OperatingSystem) + .Returns(PlatformOperatingSystem.Windows); + _mockEnvronment.Setup(x => x.Architecture) + .Returns(PlatformArchitecture.X64); + + _executor.Initialize("MonitorPostmortemDebugger;"); + Assert.IsNotNull(_settingsProvider.ActiveRunSettings); + Assert.AreEqual(string.Join(Environment.NewLine, + "", + "", + " ", + " ", + " ", + " ", + " C:\\dir\\TestResults", + " ", + " ", + " ", + " ", + " ", + " ", + " C:\\dir\\TestResults", + " ", + " ", + " ", + " ", + " ", + " ", + ""), + _settingsProvider.ActiveRunSettings.SettingsXml); + } + internal class TestableEnableBlameArgumentExecutor : EnableBlameArgumentExecutor { internal TestableEnableBlameArgumentExecutor(IRunSettingsProvider runSettingsManager, IEnvironment environment, IOutput output)