237 lines
9.7 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|