JD2022-TU1/main/extern/UbiServices/custom/external/tools/androidLauncher/AndroidLauncher/WatchDog.cs

237 lines
9.7 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
namespace AndroidLauncher
{
public class WatchDog
{
private const int WatchDogSleepTime = 4 * 1000; // 4 seconds
private const int ActivityNotFoundTimeout = 10 * 1000; // 10 seconds
private const int LogsTimeout = 5 * 60 * 1000;
private readonly object _watchDogLock = new object();
private bool _watchDogIsRunning = false;
private string _appPackageName;
private AndroidADBProcess _logProcess;
private AndroidADBProcess _fullLogProcess;
private StreamWriter _fullLogsWriter = null;
private Thread _watchDogThread;
private volatile Stopwatch _logsTimeoutWatch = new Stopwatch();
private volatile Stopwatch _activityNotFoundTimeoutWatch = new Stopwatch();
private DeviceConnectionInfo _launchDevice = null;
private List<string> _onExitStrings = null;
private string _onSuccessString = null;
private string _fullLogsPath = null;
public WatchDog(DeviceConnectionInfo launchDevice, string appPackageName, List<string> onExitStrings, string onSuccessString, string fullLogsPath)
{
_launchDevice = launchDevice;
_appPackageName = appPackageName;
_onExitStrings = onExitStrings;
_onSuccessString = onSuccessString;
_watchDogThread = new Thread(WatchTestsProcess);
_fullLogsPath = fullLogsPath;
}
public void Start()
{
_watchDogIsRunning = true;
_watchDogThread.Start();
_activityNotFoundTimeoutWatch.Reset();
_logsTimeoutWatch.Reset();
}
public bool IsAlive => _watchDogThread.IsAlive;
private void PrepareFullLogsPath()
{
if (File.Exists(_fullLogsPath))
{
Console.WriteLine("WARNING: Full logs path exists! Overwriting the old file from: " + _fullLogsPath + "!");
File.Delete(_fullLogsPath);
}
string fullLogsDirectory = new FileInfo(_fullLogsPath).Directory.FullName;
if (!Directory.Exists(fullLogsDirectory))
{
Directory.CreateDirectory(fullLogsDirectory);
}
}
private void StartLogProcesses()
{
PrepareFullLogsPath();
_fullLogProcess = new AndroidADBProcess("logcat", _launchDevice.DeviceId);
_logProcess = new AndroidADBProcess("logcat -s gtest, SimplEal, ClientSdk, DebugSDK, DEBUG, libc, AndroidRuntime, Unity, US_TEST", _launchDevice.DeviceId);
// enable raising events because Process does not raise events by default
_fullLogProcess.EnableRaisingEvents = true;
_logProcess.EnableRaisingEvents = true;
_fullLogsWriter = File.AppendText(_fullLogsPath);
_fullLogProcess.OutputDataReceived += new DataReceivedEventHandler
(
delegate (object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
_fullLogsWriter.WriteLine(e.Data);
}
}
);
// attach the event handler for OutputDataReceived before starting the process
_logProcess.OutputDataReceived += new DataReceivedEventHandler
(
delegate (object sender, DataReceivedEventArgs e)
{
if (e.Data == null)
{
Console.WriteLine("WARNING: The Watchdog logs listener returned an undefined behaviour or someone killed the ADB Server. The process should timeout shortly.");
}
else
{
Console.WriteLine("{0}", e.Data);
if (e.Data.Contains(_onSuccessString))
{
CleanUpAndExit(ExitCodes.Success);
}
else
{
foreach (string onExitString in _onExitStrings)
{
if (e.Data.Contains(onExitString))
{
CleanUpAndExit(ExitCodes.Failed);
}
}
}
_logsTimeoutWatch.Restart();
}
}
);
_logProcess.ErrorDataReceived += new DataReceivedEventHandler
(
delegate (object sender, DataReceivedEventArgs e)
{
if (e.Data != null && e.Data != "")
{
Console.WriteLine("{0}", e.Data);
}
}
);
_fullLogProcess.Start();
_logProcess.Start();
_fullLogProcess.BeginOutputReadLine();
_logProcess.BeginOutputReadLine();
}
private void WatchTestsProcess()
{
StartLogProcesses();
bool running;
lock (_watchDogLock)
{
running = _watchDogIsRunning;
}
while (true && running)
{
Thread.Sleep(WatchDogSleepTime);
lock (_watchDogLock)
{
running = _watchDogIsRunning;
// Check again if the watchdog should still run after the lock
if (running)
{
// Check if the activity is still running
int pid = Utils.FindPid(_appPackageName, _launchDevice.DeviceId);
// Start a timer when the PID is not found instead of calling directily CleanUpAndExit(ExitCodes.ActivityNotFound).
// Usefull for the edge-case where the tests activity finished the execution and ended normally, but the _watchDogLock
// is taken by the WatchTestsProcess thread. As a result, even if the exit string is encountered by the _logProcess, the _watchDogLock
// from CleanUpAndExit can't be taken until this one is released.
if (pid == Utils.PidNotFound && !_activityNotFoundTimeoutWatch.IsRunning)
{
System.Console.WriteLine("WARNING: The activity for the " + _appPackageName + " app was not found. Finishing exectuion in " + ActivityNotFoundTimeout + " milliseconds!");
_activityNotFoundTimeoutWatch.Start();
}
// If the pid detection fails right at the start of the app launch but it's
// found later on then reset the activityNotFoundTimeoutWatch
if (pid != Utils.PidNotFound && _activityNotFoundTimeoutWatch.IsRunning)
{
System.Console.WriteLine("INFO: The activity for the " + _appPackageName + " app was found later after " + _activityNotFoundTimeoutWatch.ElapsedMilliseconds + " milliseconds!");
_activityNotFoundTimeoutWatch.Reset();
}
// Call CleanUpAndExit only after the activity not found timeount was reached.
if (_activityNotFoundTimeoutWatch.Elapsed.TotalMilliseconds > ActivityNotFoundTimeout)
{
System.Console.WriteLine("ERROR: Couldn't find the running activity for " + _appPackageName + ". The activity was killed too soon or crashed. Exiting!");
CleanUpAndExit(ExitCodes.ActivityNotFound);
}
// Check if the logs timed out.
if (_logsTimeoutWatch.Elapsed.TotalMilliseconds > LogsTimeout)
{
Console.WriteLine("ERROR: Android Process didn't any log after {0} milliseconds. Either the application couldn't start or the test crash somewhere in between.", LogsTimeout);
CleanUpAndExit(ExitCodes.LogsTimeout);
}
}
}
}
}
private void CleanUpAndExit(int exitCode)
{
try
{
lock (_watchDogLock)
{
_watchDogIsRunning = false;
}
_logProcess.CancelOutputRead();
_fullLogProcess.CancelOutputRead();
_logProcess.Kill();
_fullLogProcess.Kill();
_fullLogsWriter.Close();
// If the activity coudln't be found then it means that it's already dead.
// As a result, there's no point in attempting to kill it.
if (exitCode != ExitCodes.ActivityNotFound)
{
// Kill the Android process which might be still active but in the background
// after quitting the application (per Android's policy)
Utils.StartAdbSyncWithTimeout("Killing the app!", "shell am force-stop " + _appPackageName, _launchDevice.DeviceId);
}
if (_launchDevice.LaunchTestsProcess != null)
{
_launchDevice.KillLaunchedActivity();
}
}
catch (Exception exception)
{
System.Console.WriteLine("Error on clean up: " + exception.Message);
}
Utils.StartAdbSyncWithTimeout("Kill adb server.", "kill-server");
Environment.Exit(exitCode);
}
}
}