blob: 0bd474ff429c4db29d8f38516a281c435dba176f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Apache.Ignite.Core.Tests.Process
{
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using Apache.Ignite.Core.Impl.Common;
/// <summary>
/// Defines forked Ignite node.
/// </summary>
public class IgniteProcess
{
/** Executable file name. */
private static readonly string ExeName = "Apache.Ignite.exe";
/** Executable process name. */
private static readonly string ExeProcName = ExeName.Substring(0, ExeName.LastIndexOf('.'));
/** Executable configuration file name. */
private static readonly string ExeCfgName = ExeName + ".config";
/** Executable backup configuration file name. */
private static readonly string ExeCfgBakName = ExeCfgName + ".bak";
/** Directory where binaries are stored. */
private static readonly string ExeDir;
/** Full path to executable. */
private static readonly string ExePath;
/** Full path to executable configuration file. */
private static readonly string ExeCfgPath;
/** Full path to executable configuration file backup. */
private static readonly string ExeCfgBakPath;
/** Default process output reader. */
private static readonly IIgniteProcessOutputReader DfltOutReader = new IgniteProcessConsoleOutputReader();
/** Process. */
private readonly Process _proc;
/// <summary>
/// Static initializer.
/// </summary>
static IgniteProcess()
{
// 1. Locate executable file and related stuff.
DirectoryInfo dir = new FileInfo(new Uri(typeof(IgniteProcess).Assembly.CodeBase).LocalPath).Directory;
// ReSharper disable once PossibleNullReferenceException
ExeDir = dir.FullName;
var exe = dir.GetFiles(ExeName);
if (exe.Length == 0)
throw new Exception(ExeName + " is not found in test output directory: " + dir.FullName);
ExePath = exe[0].FullName;
var exeCfg = dir.GetFiles(ExeCfgName);
if (exeCfg.Length == 0)
throw new Exception(ExeCfgName + " is not found in test output directory: " + dir.FullName);
ExeCfgPath = exeCfg[0].FullName;
ExeCfgBakPath = Path.Combine(ExeDir, ExeCfgBakName);
File.Delete(ExeCfgBakPath);
}
/// <summary>
/// Save current configuration to backup.
/// </summary>
public static void SaveConfigurationBackup()
{
File.Copy(ExeCfgPath, ExeCfgBakPath, true);
}
/// <summary>
/// Restore configuration from backup.
/// </summary>
public static void RestoreConfigurationBackup()
{
File.Copy(ExeCfgBakPath, ExeCfgPath, true);
}
/// <summary>
/// Replace application configuration with another one.
/// </summary>
/// <param name="relPath">Path to config relative to executable directory.</param>
public static void ReplaceConfiguration(string relPath)
{
File.Copy(Path.Combine(ExeDir, relPath), ExeCfgPath, true);
}
/// <summary>
/// Kill all Ignite processes.
/// </summary>
public static void KillAll()
{
foreach (Process proc in Process.GetProcesses())
{
if (proc.ProcessName.Equals(ExeProcName))
{
proc.Kill();
proc.WaitForExit();
}
}
}
/// <summary>
/// Construector.
/// </summary>
/// <param name="args">Arguments</param>
public IgniteProcess(params string[] args) : this(DfltOutReader, args) { }
/// <summary>
/// Construector.
/// </summary>
/// <param name="outReader">Output reader.</param>
/// <param name="args">Arguments.</param>
public IgniteProcess(IIgniteProcessOutputReader outReader, params string[] args)
{
// Add test dll path
args = args.Concat(new[] {"-assembly=" + GetType().Assembly.Location}).ToArray();
_proc = Start(ExePath, IgniteHome.Resolve(), outReader, args);
}
/// <summary>
/// Starts a grid process.
/// </summary>
/// <param name="exePath">Exe path.</param>
/// <param name="igniteHome">Ignite home.</param>
/// <param name="outReader">Output reader.</param>
/// <param name="args">Arguments.</param>
/// <returns>Started process.</returns>
public static Process Start(string exePath, string igniteHome, IIgniteProcessOutputReader outReader = null,
params string[] args)
{
Debug.Assert(!string.IsNullOrEmpty(exePath));
// 1. Define process start configuration.
var sb = new StringBuilder();
foreach (string arg in args)
sb.Append('\"').Append(arg).Append("\" ");
var procStart = new ProcessStartInfo
{
FileName = exePath,
Arguments = sb.ToString(),
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true
};
if (!string.IsNullOrWhiteSpace(igniteHome))
procStart.EnvironmentVariables[IgniteHome.EnvIgniteHome] = igniteHome;
procStart.EnvironmentVariables[Classpath.EnvIgniteNativeTestClasspath] = "true";
var workDir = Path.GetDirectoryName(exePath);
if (workDir != null)
procStart.WorkingDirectory = workDir;
Console.WriteLine("About to run Apache.Ignite.exe process [exePath=" + exePath + ", arguments=" + sb + ']');
// 2. Start.
var proc = Process.Start(procStart);
Debug.Assert(proc != null);
// 3. Attach output readers to avoid hangs.
AttachProcessConsoleReader(proc, outReader);
return proc;
}
/// <summary>
/// Attaches the process console reader.
/// </summary>
public static void AttachProcessConsoleReader(Process proc, IIgniteProcessOutputReader outReader = null)
{
outReader = outReader ?? DfltOutReader;
Attach(proc, proc.StandardOutput, outReader, false);
Attach(proc, proc.StandardError, outReader, true);
}
/// <summary>
/// Whether the process is still alive.
/// </summary>
public bool Alive
{
get { return !_proc.HasExited; }
}
/// <summary>
/// Gets the process.
/// </summary>
public string GetInfo()
{
return Alive
? string.Format("Id={0}, Alive={1}", _proc.Id, Alive)
: string.Format("Id={0}, Alive={1}, ExitCode={2}, ExitTime={3}",
_proc.Id, Alive, _proc.ExitCode, _proc.ExitTime);
}
/// <summary>
/// Kill process.
/// </summary>
public void Kill()
{
_proc.Kill();
}
/// <summary>
/// Suspends the process.
/// </summary>
public void Suspend()
{
_proc.Suspend();
}
/// <summary>
/// Resumes the process.
/// </summary>
public void Resume()
{
_proc.Resume();
}
/// <summary>
/// Join process.
/// </summary>
/// <returns>Exit code.</returns>
public int Join()
{
_proc.WaitForExit();
return _proc.ExitCode;
}
/// <summary>
/// Join process with timeout.
/// </summary>
/// <param name="timeout">Timeout in milliseconds.</param>
/// <param name="exitCode">Exit code.</param>
/// <returns><c>True</c> if process exit occurred before timeout.</returns>
public bool Join(int timeout, out int exitCode)
{
if (_proc.WaitForExit(timeout))
{
exitCode = _proc.ExitCode;
return true;
}
exitCode = 0;
return false;
}
/// <summary>
/// Attach output reader to the process.
/// </summary>
/// <param name="proc">Process.</param>
/// <param name="reader">Process stream reader.</param>
/// <param name="outReader">Output reader.</param>
/// <param name="err">Whether this is error stream.</param>
private static void Attach(Process proc, StreamReader reader, IIgniteProcessOutputReader outReader, bool err)
{
new Thread(() =>
{
while (!proc.HasExited)
outReader.OnOutput(proc, reader.ReadLine(), err);
}) {IsBackground = true}.Start();
}
}
}