Exemple #1
0
        private async Task <(bool Succeeded, bool Crashed, string ResultLine)> TestsSucceeded(AppBundleInformation appInfo, string test_log_path, bool timed_out)
        {
            var(resultLine, failed, crashed) = await ParseResultFile(appInfo, test_log_path, timed_out);

            // read the parsed logs in a human readable way
            if (resultLine != null)
            {
                var tests_run = resultLine.Replace("Tests run: ", "");
                if (failed)
                {
                    WrenchLog.WriteLine("AddSummary: <b>{0} failed: {1}</b><br/>", _runMode, tests_run);
                    _mainLog.WriteLine("Test run failed");
                    return(false, crashed, resultLine);
                }
                else
                {
                    WrenchLog.WriteLine("AddSummary: {0} succeeded: {1}<br/>", _runMode, tests_run);
                    _mainLog.WriteLine("Test run succeeded");
                    return(true, crashed, resultLine);
                }
            }
            else if (timed_out)
            {
                WrenchLog.WriteLine("AddSummary: <b><i>{0} timed out</i></b><br/>", _runMode);
                _mainLog.WriteLine("Test run timed out");
                return(false, false, "Test run timed out");
            }
            else
            {
                WrenchLog.WriteLine("AddSummary: <b><i>{0} crashed</i></b><br/>", _runMode);
                _mainLog.WriteLine("Test run crashed");
                return(false, true, "Test run crashed");
            }
        }
Exemple #2
0
        public async Task EndCaptureAsync(TimeSpan timeout)
        {
            // Check for crash reports
            var stopwatch = Stopwatch.StartNew();

            do
            {
                var newCrashFiles = await CreateCrashReportsSnapshotAsync();

                newCrashFiles.ExceptWith(_initialCrashes);

                if (newCrashFiles.Count == 0)
                {
                    if (stopwatch.Elapsed.TotalSeconds > timeout.TotalSeconds)
                    {
                        break;
                    }
                    else
                    {
                        _log.WriteLine(
                            "No crash reports, waiting a second to see if the crash report service just didn't complete in time ({0})",
                            (int)(timeout.TotalSeconds - stopwatch.Elapsed.TotalSeconds));

                        Thread.Sleep(TimeSpan.FromSeconds(1));
                    }

                    continue;
                }

                _log.WriteLine("Found {0} new crash report(s)", newCrashFiles.Count);

                IEnumerable <IFileBackedLog> crashReports;
                if (!_isDevice)
                {
                    crashReports = new List <IFileBackedLog>(newCrashFiles.Count);
                    foreach (var path in newCrashFiles)
                    {
                        _logs.AddFile(path, $"Crash report: {Path.GetFileName(path)}");
                    }
                }
                else
                {
                    // Download crash reports from the device. We put them in the project directory so that they're automatically deleted on wrench
                    // (if we put them in /tmp, they'd never be deleted).
                    crashReports = newCrashFiles
                                   .Select(async crash => await ProcessCrash(crash))
                                   .Select(t => t.Result)
                                   .Where(c => c != null);
                }

                foreach (var cp in crashReports)
                {
                    WrenchLog.WriteLine("AddFile: {0}", cp.FullPath);
                    _log.WriteLine("    {0}", cp.FullPath);
                }

                break;
            } while (true);
        }
Exemple #3
0
        public async Task <(TestExecutingResult ExecutingResult, string?ResultMessage)> ParseResult()
        {
            (TestExecutingResult ExecutingResult, string?ResultMessage)result = (ExecutingResult : TestExecutingResult.Finished, ResultMessage : null);
            var crashed = false;

            if (File.Exists(_listener.TestLog.FullPath))
            {
                WrenchLog.WriteLine("AddFile: {0}", _listener.TestLog.FullPath);
                (Success, crashed, result.ResultMessage) = await TestsSucceeded(_appInfo, _listener.TestLog.FullPath, _timedout);
            }
            else if (_timedout)
            {
                WrenchLog.WriteLine("AddSummary: <b><i>{0} never launched</i></b><br/>", _runMode);
                _mainLog.WriteLine("Test run never launched");
                result.ResultMessage = "Test runner never started";
                Success = false;
            }
            else if (_launchFailure)
            {
                WrenchLog.WriteLine("AddSummary: <b><i>{0} failed to launch</i></b><br/>", _runMode);
                _mainLog.WriteLine("Test run failed to launch");
                result.ResultMessage = "Test runner failed to launch";
                Success = false;
            }
            else
            {
                WrenchLog.WriteLine("AddSummary: <b><i>{0} crashed at startup (no log)</i></b><br/>", _runMode);
                _mainLog.WriteLine("Test run crashed before it started (no log file produced)");
                result.ResultMessage = "No test log file was produced";
                crashed = true;
                Success = false;
            }

            if (!Success.HasValue)
            {
                Success = false;
            }

            var crashLogWaitTime = 0;

            if (!Success.Value)
            {
                crashLogWaitTime = 5;
            }

            if (crashed)
            {
                crashLogWaitTime = 30;
            }

            await _crashReporter.EndCaptureAsync(TimeSpan.FromSeconds(crashLogWaitTime));

            if (_timedout)
            {
                result.ExecutingResult = TestExecutingResult.TimedOut;
            }
            else if (_launchFailure)
            {
                result.ExecutingResult = TestExecutingResult.LaunchFailure;
            }
            else if (_launchFailure)
            {
                result.ExecutingResult = TestExecutingResult.Crashed;
            }
            else if (Success.Value)
            {
                result.ExecutingResult = TestExecutingResult.Succeeded;
            }
            else
            {
                result.ExecutingResult = TestExecutingResult.Failed;
            }

            // Check crash reports to see if any of them explains why the test run crashed.
            if (!Success.Value)
            {
                int    pid         = -1;
                string?crashReason = null;
                foreach (var crashLog in _crashLogs)
                {
                    try
                    {
                        _logs.Add(crashLog);

                        if (pid == -1)
                        {
                            // Find the pid
                            pid = await GetPidFromMainLog();
                        }

                        GetCrashReason(pid, crashLog, out crashReason);
                        if (crashReason != null)
                        {
                            break;
                        }
                    }
                    catch (Exception e)
                    {
                        var message = string.Format("Failed to process crash report '{1}': {0}", e.Message, crashLog.Description);
                        _mainLog.WriteLine(message);
                        _exceptionLogger?.Invoke(2, message);
                    }
                }

                if (!string.IsNullOrEmpty(crashReason))
                {
                    if (crashReason == "per-process-limit")
                    {
                        result.ResultMessage = "Killed due to using too much memory (per-process-limit).";
                    }
                    else
                    {
                        result.ResultMessage = $"Killed by the OS ({crashReason})";
                    }
                }
                else if (_launchFailure)
                {
                    // same as with a crash
                    result.ResultMessage = $"Launch failure";
                }

                await GenerateXmlFailures(result.ResultMessage, crashed, crashReason);
            }

            return(result);
        }
Exemple #4
0
        private async Task <(string?resultLine, bool failed, bool crashed)> ParseResultFile(AppBundleInformation appInfo, string test_log_path, bool timed_out)
        {
            (string?resultLine, bool failed, bool crashed)parseResult = (null, false, false);
            if (!File.Exists(test_log_path))
            {
                parseResult.crashed = true; // if we do not have a log file, the test crashes
                return(parseResult);
            }
            // parsing the result is different if we are in jenkins or not.
            // When in Jenkins, Touch.Unit produces an xml file instead of a console log (so that we can get better test reporting).
            // However, for our own reporting, we still want the console-based log. This log is embedded inside the xml produced
            // by Touch.Unit, so we need to extract it and write it to disk. We also need to re-save the xml output, since Touch.Unit
            // wraps the NUnit xml output with additional information, which we need to unwrap so that Jenkins understands it.
            //
            // On the other hand, the nunit and xunit do not have that data and have to be parsed.
            //
            // This if statement has a small trick, we found out that internet sharing in some of the bots (VSTS) does not work, in
            // that case, we cannot do a TCP connection to xharness to get the log, this is a problem since if we did not get the xml
            // from the TCP connection, we are going to fail when trying to read it and not parse it. Therefore, we are not only
            // going to check if we are in CI, but also if the listener_log is valid.
            var path = Path.ChangeExtension(test_log_path, "xml");

            _resultParser.CleanXml(test_log_path, path);

            if (ResultsUseXml && _resultParser.IsValidXml(path, out var xmlType))
            {
                try
                {
                    var newFilename = _resultParser.GetXmlFilePath(path, xmlType);

                    // at this point, we have the test results, but we want to be able to have attachments in vsts, so if the format is
                    // the right one (NUnitV3) add the nodes. ATM only TouchUnit uses V3.
                    var testRunName = $"{appInfo.AppName} {appInfo.Variation}";
                    if (xmlType == XmlResultJargon.NUnitV3)
                    {
                        var logFiles = new List <string>();
                        // add our logs AND the logs of the previous task, which is the build task
                        logFiles.AddRange(Directory.GetFiles(_crashLogs.Directory));
                        if (_additionalLogsDirectory != null) // when using the run command, we do not have a build task, ergo, there are no logs to add.
                        {
                            logFiles.AddRange(Directory.GetFiles(_additionalLogsDirectory));
                        }
                        // add the attachments and write in the new filename
                        // add a final prefix to the file name to make sure that the VSTS test uploaded just pick
                        // the final version, else we will upload tests more than once
                        newFilename = XmlResultParser.GetVSTSFilename(newFilename);
                        _resultParser.UpdateMissingData(path, newFilename, testRunName, logFiles);
                    }
                    else
                    {
                        // rename the path to the correct value
                        File.Move(path, newFilename);
                    }
                    path = newFilename;

                    if (_generateHtml)
                    {
                        // write the human readable results in a tmp file, which we later use to step on the logs
                        var tmpFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
                        (parseResult.resultLine, parseResult.failed) = _resultParser.ParseResults(path, xmlType, tmpFile);
                        File.Copy(tmpFile, test_log_path, true);
                        File.Delete(tmpFile);
                    }
                    else
                    {
                        (parseResult.resultLine, parseResult.failed) = _resultParser.ParseResults(path, xmlType);
                    }

                    // we do not longer need the tmp file
                    _logs.AddFile(path, LogType.XmlLog.ToString());
                    return(parseResult);
                }
                catch (Exception e)
                {
                    _mainLog.WriteLine("Could not parse xml result file: {0}", e);
                    // print file for better debugging
                    _mainLog.WriteLine("File data is:");
                    _mainLog.WriteLine(new string('#', 10));
                    using (var stream = new StreamReader(path))
                    {
                        string?line;
                        while ((line = await stream.ReadLineAsync()) != null)
                        {
                            _mainLog.WriteLine(line);
                        }
                    }
                    _mainLog.WriteLine(new string('#', 10));
                    _mainLog.WriteLine("End of xml results.");
                    if (timed_out)
                    {
                        WrenchLog.WriteLine($"AddSummary: <b><i>{_runMode} timed out</i></b><br/>");
                        return(parseResult);
                    }
                    else
                    {
                        WrenchLog.WriteLine($"AddSummary: <b><i>{_runMode} crashed</i></b><br/>");
                        _mainLog.WriteLine("Test run crashed");
                        parseResult.crashed = true;
                        return(parseResult);
                    }
                }
            }
            // delete not needed copy
            File.Delete(path);

            // not the most efficient way but this just happens when we run
            // the tests locally and we usually do not run all tests, we are
            // more interested to be efficent on the bots
            (parseResult.resultLine, parseResult.failed) = await GetResultLine(test_log_path);

            return(parseResult);
        }
    public async Task EndCaptureAsync(TimeSpan timeout)
    {
        if (_initialCrashes == null)
        {
            throw new InvalidOperationException("CrashSnapshotReport capturing was ended without being started first!");
        }

        _log.WriteLine($"No crash reports, waiting {(int)timeout.TotalSeconds} seconds for the crash report service...");

        // Check for crash reports
        var stopwatch = Stopwatch.StartNew();

        do
        {
            var newCrashFiles = await CreateCrashReportsSnapshotAsync();

            newCrashFiles.ExceptWith(_initialCrashes);

            if (newCrashFiles.Count == 0)
            {
                if (stopwatch.Elapsed.TotalSeconds > timeout.TotalSeconds)
                {
                    break;
                }
                else
                {
                    await Task.Delay(TimeSpan.FromSeconds(1));
                }

                continue;
            }

            _log.WriteLine("Found {0} new crash report(s)", newCrashFiles.Count);

            IEnumerable <IFileBackedLog> crashReports;
            if (!_isDevice)
            {
                crashReports = new List <IFileBackedLog>(newCrashFiles.Count);
                foreach (var path in newCrashFiles)
                {
                    // It can happen that the crash log is still being written to so we have to retry
                    int retry = 1;
                    while (true)
                    {
                        try
                        {
                            var fileName = Path.GetFileName(path);
                            _log.WriteLine($"  - Adding {path}");
                            _logs.AddFile(path, $"Crash report: {fileName}");
                            _log.WriteLine($"    Successfully copied {fileName}");
                            break;
                        }
                        catch (Exception e)
                        {
                            _log.WriteLine($"    Attempt {retry} to copy a crash report failed: {e.Message}");
                        }

                        if (retry == 3)
                        {
                            _log.WriteLine($"    Failed to copy a crash report after {retry} retries");
                            break;
                        }

                        ++retry;
                        await Task.Delay(TimeSpan.FromSeconds(2 * retry));
                    }
                }
            }
            else
            {
                // Download crash reports from the device. We put them in the project directory so that they're automatically deleted on wrench
                // (if we put them in /tmp, they'd never be deleted).
                crashReports = newCrashFiles
                               .Select(async crash => await ProcessCrash(crash))
                               .Select(t => t.Result)
                               .Where(c => c != null);
            }

            foreach (var cp in crashReports)
            {
                WrenchLog.WriteLine("AddFile: {0}", cp.FullPath);
                _log.WriteLine("    {0}", cp.FullPath);
            }

            break;
        } while (true);
    }