在 C# DLL 生命周期内运行并终止 Selenium 服务器单例进程
我有一个使用 NUnit 加载的Selenium Test .DLL。
当测试运行时,我运行一个所需的Selenium Java Server,悄悄隐藏在进程中。
但是,我目前在测试开始时启动服务器,并在测试时Kill()
它停止。
这会导致 selenium 服务器针对每个测试启动/停止。
我想要的是 Selenium 服务器进程:
- 在DLL加载/初始化时启动或在第一个测试开始时
- < code>Kill()ed DLL 死亡 或垃圾回收
我读到 C# 不支持捕获 DLL 初始化和调用代码。 (我错了吗?)
我的想法是将 Selenium 服务器托管在一个单例类中,并在第一次测试运行时对其进行初始化。然后,我会将其留给垃圾收集,以通过解构函数调用 Dispose 方法。我目前有以下代码来托管 selenium 服务器:
namespace Tests.Server
{
using System;
using System.Diagnostics;
using System.IO;
using System.Security;
using System.Windows.Forms;
using Microsoft.Win32;
/// <summary>
/// A singleton class to host and control the selenium server.
/// </summary>
public class SeleniumServer : IDisposable
{
#region Fields
/// <summary>
/// The singleton instance
/// </summary>
private static volatile SeleniumServer instance;
/// <summary>
/// An object to perform double-check locking upon instance creation to
/// avoid dead-locks.
/// See <see href="SeleniumServer.Instance"/> for more information.
/// </summary>
private static object syncRoot = new Object();
/// <summary>
/// The current selenium server.
/// </summary>
private Process seleniumServer = null;
/// <summary>
/// A flag for the disposal of the class.
/// </summary>
private bool isDisposed = false;
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of the SeleniumServer class. Starts the
/// Selenium Java server in a background hidden thread.
/// </summary>
private SeleniumServer()
{
// Get the java install folder.
string javaFileLocation = this.GetJavaFileLocation();
// Get the selenium server java executable
string jarFileLocation = '"' + Directory.GetCurrentDirectory() + @"\SeleniumServer\selenium-server.jar""";
// Start the selenium server
seleniumServer = new Process();
seleniumServer.StartInfo.FileName = javaFileLocation;
seleniumServer.StartInfo.Arguments = " -jar " + jarFileLocation + " -browserSessionReuse -trustAllSSLCertification";
seleniumServer.StartInfo.WorkingDirectory = jarFileLocation.Substring(0, jarFileLocation.LastIndexOf("\\"));
seleniumServer.StartInfo.UseShellExecute = true;
seleniumServer.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
seleniumServer.Start();
}
#endregion
#region Deconstructor
~SeleniumServer()
{
Dispose(false);
}
#endregion
#region Properties
/// <summary>
/// A thread safe
/// </summary>
public static SeleniumServer Instance
{
get
{
// This approach ensures that only one instance is created and
// only when the instance is needed. Also, the variable is
// declared to be volatile to ensure that assignment to the
// instance variable completes before the instance variable can
// be accessed. Lastly, this approach uses a syncRoot instance
// to lock on, rather than locking on the type itself, to avoid
// deadlocks.
// This double-check locking approach solves the thread
// concurrency problems while avoiding an exclusive lock in
// every call to the Instance property method. It also allows
// you to delay instantiation until the object is first
// accessed. In practice, an application rarely requires this
// type of implementation. In most cases, the static
// initialization approach is sufficient.
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
{
instance = new SeleniumServer();
}
}
}
return instance;
}
}
#endregion
#region Methods
/// <summary>
/// Stops the process.
/// </summary>
protected void Dispose(bool disposing)
{
if (disposing)
{
// Code to dispose the managed resources of the class
// Not needed right now.
}
// Code to dispose the un-managed resources of the class
// TODO: Handle the exceptions
Instance.seleniumServer.Kill();
// All done!
isDisposed = true;
}
/// <summary>
/// Dispose of the class and stop the Selenium server process.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Stops the Selenium Server.
/// </summary>
public void Stop()
{
this.Dispose();
}
/// <summary>
/// Attempts to get the Java installation folder from the registry. If
/// it cannot be found the default installation folder is checked before
/// failing.
/// </summary>
/// <returns>The Java executable file path.</returns>
/// <exception cref="System.Exception">
/// Thrown when the user does not have permission to access the
/// registry.
/// </exception>
/// <exception cref="System.ObjectDisposedException">
/// Thrown if the registry key object is disposed of.
/// </exception>
/// <exception cref="System.IOException">
/// Thrown if the registry key object is marked for deletion.
/// </exception>
private string GetJavaFileLocation()
{
string javaFileLocation = string.Empty;
RegistryKey regKey = Registry.LocalMachine;
RegistryKey subKey = null;
try
{
// Check for Java in the native bitness
string javaRegistryLocation = @"SOFTWARE\JavaSoft\Java Runtime Environment\";
subKey = regKey.OpenSubKey(javaRegistryLocation);
// Check if we are running in WOW64 and only 32 bit Java is installed.
if (subKey == null)
{
javaRegistryLocation = @"SOFTWARE\Wow6432Node\JavaSoft\Java Runtime Environment\";
subKey = regKey.OpenSubKey(javaRegistryLocation);
}
// If Java was not found in either of these location the user
// needs to install it. Note that Java 32 bit is a prerequisite
// for the installer so should always be installed.
if (subKey == null)
{
MessageBox.Show(
"You must have Java installed to run the commands server. This allows browser commands to be routed to the browser window.\n\nPlease visit: http://www.java.com/ to download.",
"Please Install Java",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
throw new Exception("No installation of Java was detected to run the selenium server.");
}
// Get all the sub keys (could be multiple versions of Java
// installed) and get the most recent (last one)
string[] subKeyNames = subKey.GetSubKeyNames();
subKey = regKey.OpenSubKey(javaRegistryLocation + subKeyNames[subKeyNames.Length - 1]);
// Specify the java executable location
javaFileLocation = subKey.GetValue("JavaHome").ToString() + @"\bin\java.exe";
}
catch (SecurityException e)
{
// Attempt to find the java executable at the default location.
javaFileLocation = @"C:\Program Files\Java\jre6\bin\java.exe";
if (!File.Exists(javaFileLocation))
{
MessageBox.Show(
"The program did not have permission to access the registry to obtain the installation folder of Java.\n\nThe default location (" + javaFileLocation + ") of Java was used and the Java executable was not found.\n\nPlease install Java in this folder or see the help under the RegistryKey.OpenSubKey method on MSDN.",
"Java Executable Not Found",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
throw new Exception("The program did not have permission to access the registry to obtain the installation folder of Java.", e);
}
}
catch (UnauthorizedAccessException e)
{
// Attempt to find the java executable at the default location.
javaFileLocation = @"C:\Program Files\Java\jre6\bin\java.exe";
if (!File.Exists(javaFileLocation))
{
MessageBox.Show(
"The user does not have the necessary registry rights to obtain the installation folder of Java.\n\nThe default location (" + javaFileLocation + ") of Java was used and the Java executable was not found.\n\nPlease install Java in this folder or see the help under the RegistryKey.OpenSubKey method on MSDN.",
"Java Executable Not Found",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
throw new Exception("The user does not have the necessary registry rights to obtain the installation folder of Java.", e);
}
}
catch (ObjectDisposedException e)
{
// This hopefully shouldn't happen so ask the user to report it.
MessageBox.Show(
"The Java registry object was closed, resulting in the Java server not being started. Please report this error.",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
throw new ObjectDisposedException("The Java registry object was closed. Please report this error.", e);
}
catch (IOException e)
{
// This hopefully shouldn't happen so ask the user to report it.
MessageBox.Show(
"The Java registry object was marked for deletion, resulting in the Java server not being started. Please report this error.",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
throw new ObjectDisposedException("The Java registry object was marked for deletion. Please report this error.", e);
}
return javaFileLocation;
}
#endregion
}
}
我的问题是:
- 这是在 DLL 生命周期内托管进程的明智方法吗?
- 如何从我的代码中设置此类?它需要静态/全局吗?您知道有什么使用单例的好例子吗?
- DLL 卸载时,垃圾收集会处理该单例吗?
- 我是否可以将该进程托管为 DLL 的子进程,即使 DLL 不会持续活动,因为 NUnit 只会在离散时间点执行 DLL 中的测试?
I have a Selenium Test .DLL that is loaded using NUnit.
I run a required Selenium Java Server quietly hidden in a process when a test is run.
However I currently start the server when a test is started and Kill()
it when the test stops.
This results in the selenium server starting/stopping for every test.
What I want is for the Selenium Server process to:
- Start either on DLL load/initialization or when the first test starts
- Be
Kill()
ed on DLL death or Garbage Collection
I read that C# does not support catching DLL initialization and calling code. (Am i wrong?)
My idea was to host the Selenium server in a singleton class and initialize it when the first test ran. I would then leave it to the Garbage Collection to call the Dispose method via the deconstructor. I currently have the following code to host the selenium server:
namespace Tests.Server
{
using System;
using System.Diagnostics;
using System.IO;
using System.Security;
using System.Windows.Forms;
using Microsoft.Win32;
/// <summary>
/// A singleton class to host and control the selenium server.
/// </summary>
public class SeleniumServer : IDisposable
{
#region Fields
/// <summary>
/// The singleton instance
/// </summary>
private static volatile SeleniumServer instance;
/// <summary>
/// An object to perform double-check locking upon instance creation to
/// avoid dead-locks.
/// See <see href="SeleniumServer.Instance"/> for more information.
/// </summary>
private static object syncRoot = new Object();
/// <summary>
/// The current selenium server.
/// </summary>
private Process seleniumServer = null;
/// <summary>
/// A flag for the disposal of the class.
/// </summary>
private bool isDisposed = false;
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of the SeleniumServer class. Starts the
/// Selenium Java server in a background hidden thread.
/// </summary>
private SeleniumServer()
{
// Get the java install folder.
string javaFileLocation = this.GetJavaFileLocation();
// Get the selenium server java executable
string jarFileLocation = '"' + Directory.GetCurrentDirectory() + @"\SeleniumServer\selenium-server.jar""";
// Start the selenium server
seleniumServer = new Process();
seleniumServer.StartInfo.FileName = javaFileLocation;
seleniumServer.StartInfo.Arguments = " -jar " + jarFileLocation + " -browserSessionReuse -trustAllSSLCertification";
seleniumServer.StartInfo.WorkingDirectory = jarFileLocation.Substring(0, jarFileLocation.LastIndexOf("\\"));
seleniumServer.StartInfo.UseShellExecute = true;
seleniumServer.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
seleniumServer.Start();
}
#endregion
#region Deconstructor
~SeleniumServer()
{
Dispose(false);
}
#endregion
#region Properties
/// <summary>
/// A thread safe
/// </summary>
public static SeleniumServer Instance
{
get
{
// This approach ensures that only one instance is created and
// only when the instance is needed. Also, the variable is
// declared to be volatile to ensure that assignment to the
// instance variable completes before the instance variable can
// be accessed. Lastly, this approach uses a syncRoot instance
// to lock on, rather than locking on the type itself, to avoid
// deadlocks.
// This double-check locking approach solves the thread
// concurrency problems while avoiding an exclusive lock in
// every call to the Instance property method. It also allows
// you to delay instantiation until the object is first
// accessed. In practice, an application rarely requires this
// type of implementation. In most cases, the static
// initialization approach is sufficient.
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
{
instance = new SeleniumServer();
}
}
}
return instance;
}
}
#endregion
#region Methods
/// <summary>
/// Stops the process.
/// </summary>
protected void Dispose(bool disposing)
{
if (disposing)
{
// Code to dispose the managed resources of the class
// Not needed right now.
}
// Code to dispose the un-managed resources of the class
// TODO: Handle the exceptions
Instance.seleniumServer.Kill();
// All done!
isDisposed = true;
}
/// <summary>
/// Dispose of the class and stop the Selenium server process.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Stops the Selenium Server.
/// </summary>
public void Stop()
{
this.Dispose();
}
/// <summary>
/// Attempts to get the Java installation folder from the registry. If
/// it cannot be found the default installation folder is checked before
/// failing.
/// </summary>
/// <returns>The Java executable file path.</returns>
/// <exception cref="System.Exception">
/// Thrown when the user does not have permission to access the
/// registry.
/// </exception>
/// <exception cref="System.ObjectDisposedException">
/// Thrown if the registry key object is disposed of.
/// </exception>
/// <exception cref="System.IOException">
/// Thrown if the registry key object is marked for deletion.
/// </exception>
private string GetJavaFileLocation()
{
string javaFileLocation = string.Empty;
RegistryKey regKey = Registry.LocalMachine;
RegistryKey subKey = null;
try
{
// Check for Java in the native bitness
string javaRegistryLocation = @"SOFTWARE\JavaSoft\Java Runtime Environment\";
subKey = regKey.OpenSubKey(javaRegistryLocation);
// Check if we are running in WOW64 and only 32 bit Java is installed.
if (subKey == null)
{
javaRegistryLocation = @"SOFTWARE\Wow6432Node\JavaSoft\Java Runtime Environment\";
subKey = regKey.OpenSubKey(javaRegistryLocation);
}
// If Java was not found in either of these location the user
// needs to install it. Note that Java 32 bit is a prerequisite
// for the installer so should always be installed.
if (subKey == null)
{
MessageBox.Show(
"You must have Java installed to run the commands server. This allows browser commands to be routed to the browser window.\n\nPlease visit: http://www.java.com/ to download.",
"Please Install Java",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
throw new Exception("No installation of Java was detected to run the selenium server.");
}
// Get all the sub keys (could be multiple versions of Java
// installed) and get the most recent (last one)
string[] subKeyNames = subKey.GetSubKeyNames();
subKey = regKey.OpenSubKey(javaRegistryLocation + subKeyNames[subKeyNames.Length - 1]);
// Specify the java executable location
javaFileLocation = subKey.GetValue("JavaHome").ToString() + @"\bin\java.exe";
}
catch (SecurityException e)
{
// Attempt to find the java executable at the default location.
javaFileLocation = @"C:\Program Files\Java\jre6\bin\java.exe";
if (!File.Exists(javaFileLocation))
{
MessageBox.Show(
"The program did not have permission to access the registry to obtain the installation folder of Java.\n\nThe default location (" + javaFileLocation + ") of Java was used and the Java executable was not found.\n\nPlease install Java in this folder or see the help under the RegistryKey.OpenSubKey method on MSDN.",
"Java Executable Not Found",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
throw new Exception("The program did not have permission to access the registry to obtain the installation folder of Java.", e);
}
}
catch (UnauthorizedAccessException e)
{
// Attempt to find the java executable at the default location.
javaFileLocation = @"C:\Program Files\Java\jre6\bin\java.exe";
if (!File.Exists(javaFileLocation))
{
MessageBox.Show(
"The user does not have the necessary registry rights to obtain the installation folder of Java.\n\nThe default location (" + javaFileLocation + ") of Java was used and the Java executable was not found.\n\nPlease install Java in this folder or see the help under the RegistryKey.OpenSubKey method on MSDN.",
"Java Executable Not Found",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
throw new Exception("The user does not have the necessary registry rights to obtain the installation folder of Java.", e);
}
}
catch (ObjectDisposedException e)
{
// This hopefully shouldn't happen so ask the user to report it.
MessageBox.Show(
"The Java registry object was closed, resulting in the Java server not being started. Please report this error.",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
throw new ObjectDisposedException("The Java registry object was closed. Please report this error.", e);
}
catch (IOException e)
{
// This hopefully shouldn't happen so ask the user to report it.
MessageBox.Show(
"The Java registry object was marked for deletion, resulting in the Java server not being started. Please report this error.",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
throw new ObjectDisposedException("The Java registry object was marked for deletion. Please report this error.", e);
}
return javaFileLocation;
}
#endregion
}
}
My questions are:
- Is this a sensible way to host a process for a DLL lifetime?
- How do I set up this class from my code? Does it need to static/global? Any good examples of using singletons you know of?
- Will garbage collection dispose of this singleton upon DLL unload?
- Can I host the process as a child process of my DLL, even though the DLL will not be continuously active as NUnit will only execute the tests in the DLL at discrete timepoints?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
管理它:
只需要做:
在SetUp开始时启动服务器。
我想我会发帖以防有人搜索这个问题。
Managed it:
The just do:
At the start of the SetUp to start the server.
Thought I'd post in case anyone searches for this problem.