diff --git a/Directory.Build.targets b/Directory.Build.targets
index 9ba3a39f5d..92406f90f8 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -42,6 +42,7 @@
$(DefineConstants);FEATURE_ARGITERATOR
$(DefineConstants);FEATURE_DICTIONARY_REMOVE_CONTINUEENUMERATION
+ $(DefineConstants);FEATURE_PROCESS_KILL_ENTIREPROCESSTREE
$(DefineConstants);FEATURE_STRING_CONCAT_READONLYSPAN
diff --git a/src/Lucene.Net.TestFramework/Support/Util/RandomizedContext.cs b/src/Lucene.Net.TestFramework/Support/Util/RandomizedContext.cs
index df7aa42032..a2155517f7 100644
--- a/src/Lucene.Net.TestFramework/Support/Util/RandomizedContext.cs
+++ b/src/Lucene.Net.TestFramework/Support/Util/RandomizedContext.cs
@@ -49,7 +49,7 @@ public RandomizedContext(Test currentTest, Assembly currentTestAssembly, long ra
this.currentTest = currentTest ?? throw new ArgumentNullException(nameof(currentTest));
this.currentTestAssembly = currentTestAssembly ?? throw new ArgumentNullException(nameof(currentTestAssembly));
this.randomSeed = randomSeed;
- this.randomSeedAsHex = "0x" + randomSeed.ToHexString();
+ this.randomSeedAsHex = SeedUtils.FormatSeed(randomSeed);
this.testSeed = testSeed;
}
diff --git a/src/Lucene.Net.TestFramework/Support/Util/SeedUtils.cs b/src/Lucene.Net.TestFramework/Support/Util/SeedUtils.cs
new file mode 100644
index 0000000000..5da49989bb
--- /dev/null
+++ b/src/Lucene.Net.TestFramework/Support/Util/SeedUtils.cs
@@ -0,0 +1,22 @@
+using J2N;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Lucene.Net.Util
+{
+ ///
+ /// Utilities for parsing and formatting random seeds.
+ ///
+ internal static class SeedUtils
+ {
+ ///
+ /// Format a single .
+ ///
+ // LUCENENET: Our format deviates from the Java randomizedtesting implementation
+ public static string FormatSeed(long seed)
+ => string.Concat("0x", seed.ToHexString());
+ }
+}
diff --git a/src/Lucene.Net.TestFramework/Util/LuceneTestCase.cs b/src/Lucene.Net.TestFramework/Util/LuceneTestCase.cs
index dc28893a26..988b63faf4 100644
--- a/src/Lucene.Net.TestFramework/Util/LuceneTestCase.cs
+++ b/src/Lucene.Net.TestFramework/Util/LuceneTestCase.cs
@@ -2919,8 +2919,8 @@ public static DirectoryInfo CreateTempDir()
}
///
- /// Creates an empty, temporary folder with the given name prefix under the
- /// system's .
+ /// Creates an empty, temporary folder with the given name under the
+ /// system's or if supplied, the tempDir system property.
///
/// The folder will be automatically removed after the
/// test class completes successfully. The test should close any file handles that would prevent
@@ -2929,6 +2929,7 @@ public static DirectoryInfo CreateTempDir()
public static DirectoryInfo CreateTempDir(string prefix)
{
//DirectoryInfo @base = BaseTempDirForTestClass();
+ string @base = SystemProperties.GetProperty("tempDir", System.IO.Path.GetTempPath());
int attempt = 0;
DirectoryInfo f;
@@ -2942,7 +2943,7 @@ public static DirectoryInfo CreateTempDir(string prefix)
// LUCENENET specific - need to use a random file name instead of a sequential one or two threads may attempt to do
// two operations on a file at the same time.
//f = new DirectoryInfo(Path.Combine(System.IO.Path.GetTempPath(), "LuceneTemp", prefix + "-" + attempt));
- f = new DirectoryInfo(Path.Combine(System.IO.Path.GetTempPath(), "LuceneTemp", prefix + "-" + Path.GetFileNameWithoutExtension(Path.GetRandomFileName())));
+ f = new DirectoryInfo(Path.Combine(@base, "LuceneTemp", prefix + "-" + Path.GetFileNameWithoutExtension(Path.GetRandomFileName())));
try
{
@@ -2963,19 +2964,20 @@ public static DirectoryInfo CreateTempDir(string prefix)
}
///
- /// Creates an empty file with the given prefix and suffix under the
- /// system's .
+ /// Creates an empty file with the given and under the
+ /// system's or if supplied, the tempDir system property.
///
/// The file will be automatically removed after the
/// test class completes successfully. The test should close any file handles that would prevent
- /// the folder from being removed.
+ /// the file from being removed.
///
public static FileInfo CreateTempFile(string prefix, string suffix)
{
//DirectoryInfo @base = BaseTempDirForTestClass();
+ string @base = SystemProperties.GetProperty("tempDir", System.IO.Path.GetTempPath());
//int attempt = 0;
- FileInfo f = FileSupport.CreateTempFile(prefix, suffix, new DirectoryInfo(Path.GetTempPath()));
+ FileInfo f = FileSupport.CreateTempFile(prefix, suffix, @base);
//do
//{
// if (attempt++ >= TEMP_NAME_RETRY_THRESHOLD)
@@ -2992,6 +2994,10 @@ public static FileInfo CreateTempFile(string prefix, string suffix)
///
/// Creates an empty temporary file.
+ ///
+ /// The file will be automatically removed after the
+ /// test class completes successfully. The test should close any file handles that would prevent
+ /// the file from being removed.
///
///
public static FileInfo CreateTempFile()
diff --git a/src/Lucene.Net.Tests._I-J/Lucene.Net.Tests._I-J.csproj b/src/Lucene.Net.Tests._I-J/Lucene.Net.Tests._I-J.csproj
index dec0b4aff6..22ff584546 100644
--- a/src/Lucene.Net.Tests._I-J/Lucene.Net.Tests._I-J.csproj
+++ b/src/Lucene.Net.Tests._I-J/Lucene.Net.Tests._I-J.csproj
@@ -86,6 +86,14 @@
+
+
+
+ <_Parameter1>TargetFramework
+ <_Parameter2>$(TargetFramework)
+
+
+
diff --git a/src/Lucene.Net.Tests/Index/TestIndexWriterOnJRECrash.cs b/src/Lucene.Net.Tests/Index/TestIndexWriterOnJRECrash.cs
index cd8e42f9b5..c5cffc3878 100644
--- a/src/Lucene.Net.Tests/Index/TestIndexWriterOnJRECrash.cs
+++ b/src/Lucene.Net.Tests/Index/TestIndexWriterOnJRECrash.cs
@@ -1,282 +1,379 @@
-// LUCENENET NOTE: Clearly this test is not applicable to .NET, but just
-// adding the file to the project for completedness.
-
-//using System;
-//using System.Collections.Generic;
-//using System.Threading;
-//using Lucene.Net.Randomized;
-//using Lucene.Net.Randomized.Generators;
-//using Console = Lucene.Net.Util.SystemConsole;
-
-//namespace Lucene.Net.Index
-//{
-
-// /*
-// /// 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.
-// ///
-// */
-
-
-// using Codec = Lucene.Net.Codecs.Codec;
-// using BaseDirectoryWrapper = Lucene.Net.Store.BaseDirectoryWrapper;
-// using Constants = Lucene.Net.Util.Constants;
-// using TestUtil = Lucene.Net.Util.TestUtil;
-
-// using NUnit.Framework;
-// using Lucene.Net.Support;
-// using System.IO;
-// ///
-// /// Runs TestNRTThreads in a separate process, crashes the JRE in the middle
-// /// of execution, then runs checkindex to make sure its not corrupt.
-// ///
-// [TestFixture]
-// public class TestIndexWriterOnJRECrash : TestNRTThreads
-// {
-// private DirectoryInfo TempDir;
-
-// [SetUp]
-// public override void SetUp()
-// {
-// base.SetUp();
-// TempDir = CreateTempDir("jrecrash");
-// TempDir.Delete();
-// TempDir.mkdir();
-// }
-
-// [Test]
-// public override void TestNRTThreads_Mem()
-// {
-// // if we are not the fork
-// if (System.getProperty("tests.crashmode") is null)
-// {
-// // try up to 10 times to create an index
-// for (int i = 0; i < 10; i++)
-// {
-// ForkTest();
-// // if we succeeded in finding an index, we are done.
-// if (CheckIndexes(TempDir))
-// {
-// return;
-// }
-// }
-// }
-// else
-// {
-// // TODO: the non-fork code could simply enable impersonation?
-// AssumeFalse("does not support PreFlex, see LUCENE-3992", Codec.Default.Name.Equals("Lucene3x", StringComparison.Ordinal));
-// // we are the fork, setup a crashing thread
-// int crashTime = TestUtil.NextInt(Random(), 3000, 4000);
-// ThreadJob t = new ThreadAnonymousClass(this, crashTime);
-// t.Priority = ThreadPriority.Highest;
-// t.Start();
-// // run the test until we crash.
-// for (int i = 0; i < 1000; i++)
-// {
-// base.TestNRTThreads_Mem();
-// }
-// }
-// }
-
-// private sealed class ThreadAnonymousClass : ThreadJob
-// {
-// private readonly TestIndexWriterOnJRECrash outerInstance;
-
-// private int CrashTime;
-
-// public ThreadAnonymousClass(TestIndexWriterOnJRECrash outerInstance, int crashTime)
-// {
-// this.outerInstance = outerInstance;
-// this.CrashTime = crashTime;
-// }
-
-// public override void Run()
-// {
-// try
-// {
-// Thread.Sleep(CrashTime);
-// }
-// catch (Exception e) when (e.IsInterruptedException())
-// {
-// }
-// outerInstance.CrashJRE();
-// }
-// }
-
-// ///
-// /// fork ourselves in a new jvm. sets -Dtests.crashmode=true
-// public virtual void ForkTest()
-// {
-// IList cmd = new JCG.List();
-// cmd.Add(System.getProperty("java.home") + System.getProperty("file.separator") + "bin" + System.getProperty("file.separator") + "java");
-// cmd.Add("-Xmx512m");
-// cmd.Add("-Dtests.crashmode=true");
-// // passing NIGHTLY to this test makes it run for much longer, easier to catch it in the act...
-// cmd.Add("-Dtests.nightly=true");
-// cmd.Add("-DtempDir=" + TempDir.Path);
-// cmd.Add("-Dtests.seed=" + SeedUtils.formatSeed(Random().NextLong()));
-// cmd.Add("-ea");
-// cmd.Add("-cp");
-// cmd.Add(System.getProperty("java.class.path"));
-// cmd.Add("org.junit.runner.JUnitCore");
-// cmd.Add(this.GetType().Name);
-// ProcessBuilder pb = new ProcessBuilder(cmd);
-// pb.directory(TempDir);
-// pb.redirectErrorStream(true);
-// Process p = pb.Start();
-
-// // We pump everything to stderr.
-// PrintStream childOut = System.err;
-// Thread stdoutPumper = ThreadPumper.Start(p.InputStream, childOut);
-// Thread stderrPumper = ThreadPumper.Start(p.ErrorStream, childOut);
-// if (VERBOSE)
-// {
-// childOut.println(">>> Begin subprocess output");
-// }
-// p.waitFor();
-// stdoutPumper.Join();
-// stderrPumper.Join();
-// if (VERBOSE)
-// {
-// childOut.println("<<< End subprocess output");
-// }
-// }
-
-// ///
-// /// A pipe thread. It'd be nice to reuse guava's implementation for this...
-// internal class ThreadPumper
-// {
-// public static Thread Start(InputStream from, OutputStream to)
-// {
-// ThreadJob t = new ThreadAnonymousClass2(from, to);
-// t.Start();
-// return t;
-// }
-
-// private sealed class ThreadAnonymousClass2 : ThreadJob
-// {
-// private InputStream From;
-// private OutputStream To;
-
-// public ThreadAnonymousClass2(InputStream from, OutputStream to)
-// {
-// this.From = from;
-// this.To = to;
-// }
-
-// public override void Run()
-// {
-// try
-// {
-// sbyte[] buffer = new sbyte[1024];
-// int len;
-// while ((len = From.Read(buffer)) != -1)
-// {
-// if (VERBOSE)
-// {
-// To.Write(buffer, 0, len);
-// }
-// }
-// }
-// catch (Exception e) when (e.IsIOException())
-// {
-// Console.Error.WriteLine("Couldn't pipe from the forked process: " + e.ToString());
-// }
-// }
-// }
-// }
-
-// ///
-// /// Recursively looks for indexes underneath file
,
-// /// and runs checkindex on them. returns true if it found any indexes.
-// ///
-// public virtual bool CheckIndexes(DirectoryInfo file)
-// {
-// if (file.IsDirectory)
-// {
-// BaseDirectoryWrapper dir = NewFSDirectory(file);
-// dir.CheckIndexOnClose = false; // don't double-checkindex
-// if (DirectoryReader.IndexExists(dir))
-// {
-// if (VERBOSE)
-// {
-// Console.Error.WriteLine("Checking index: " + file);
-// }
-// // LUCENE-4738: if we crashed while writing first
-// // commit it's possible index will be corrupt (by
-// // design we don't try to be smart about this case
-// // since that too risky):
-// if (SegmentInfos.GetLastCommitGeneration(dir) > 1)
-// {
-// TestUtil.CheckIndex(dir);
-// }
-// dir.Dispose();
-// return true;
-// }
-// dir.Dispose();
-// foreach (FileInfo f in file.ListAll())
-// {
-// if (CheckIndexes(f))
-// {
-// return true;
-// }
-// }
-// }
-// return false;
-// }
-
-// ///
-// /// currently, this only works/tested on Sun and IBM.
-// ///
-// public virtual void CrashJRE()
-// {
-// string vendor = Constants.JAVA_VENDOR;
-// bool supportsUnsafeNpeDereference = vendor.StartsWith("Oracle", StringComparison.Ordinal) || vendor.StartsWith("Sun", StringComparison.Ordinal) || vendor.StartsWith("Apple", StringComparison.Ordinal);
-
-// try
-// {
-// if (supportsUnsafeNpeDereference)
-// {
-// try
-// {
-// Type clazz = Type.GetType("sun.misc.Unsafe");
-// Field field = clazz.GetDeclaredField("theUnsafe");
-// field.Accessible = true;
-// object o = field.Get(null);
-// Method m = clazz.GetMethod("putAddress", typeof(long), typeof(long));
-// m.invoke(o, 0L, 0L);
-// }
-// catch (Exception e)
-// {
-// Console.WriteLine("Couldn't kill the JVM via Unsafe.");
-// Console.WriteLine(e.StackTrace);
-// }
-// }
-
-// // Fallback attempt to Runtime.halt();
-// Runtime.Runtime.halt(-1);
-// }
-// catch (Exception e)
-// {
-// Console.WriteLine("Couldn't kill the JVM.");
-// Console.WriteLine(e.StackTrace);
-// }
-
-// // We couldn't get the JVM to crash for some reason.
-// Assert.Fail();
-// }
-// }
-
-//}
\ No newline at end of file
+// Lucene version compatibility level 4.8.1
+using J2N.Threading;
+using Lucene.Net.Util;
+using NUnit.Framework;
+using RandomizedTesting.Generators;
+using System;
+using System.Data;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading;
+using BaseDirectoryWrapper = Lucene.Net.Store.BaseDirectoryWrapper;
+using Console = Lucene.Net.Util.SystemConsole;
+
+namespace Lucene.Net.Index
+{
+ /*
+ * 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.
+ */
+
+ ///
+ /// Runs TestNRTThreads in a separate process, crashes the JRE in the middle
+ /// of execution, then runs checkindex to make sure its not corrupt.
+ ///
+ [SuppressCodecs("SimpleText", "Memory", "Direct", "Lucene3x")]
+ [TestFixture]
+ public class TestIndexWriterOnJRECrash : TestNRTThreads
+ {
+ // LUCENENET: Setup unnecessary because we create a new temp directory
+ // in each iteration of the test.
+
+ [Test]
+ [Slow]
+ public override void TestNRTThreads_Mem()
+ {
+ //if we are not the fork
+ if (SystemProperties.GetProperty("tests:crashmode") is null)
+ {
+ // try up to 10 times to create an index
+ for (int i = 0; i < 10; i++)
+ {
+ // LUCENENET: We create a new temp folder on each iteration to ensure
+ // there is only 1 index to check below it. Otherwise we could just
+ // get a cascade of empty checks because the first one contains an index that
+ // crashed upon the first commit. The subdirectories would otherwise be checked in
+ // lexicographical order rather than checking the one we create in the current iteration.
+ DirectoryInfo tempDir = CreateTempDir("netcrash");
+
+ FileInfo tempProcessToKillFile = CreateTempFile(prefix: "netcrash-processToKill", suffix: ".txt");
+ tempProcessToKillFile.Delete(); // We use the creation of this file as a signal to parse it.
+
+ // Note this is the vstest.console process we are tracking here.
+ Process p = ForkTest(tempDir.FullName, tempProcessToKillFile.FullName);
+
+ TextWriter childOut = BeginOutput(p, out ThreadJob stdOutPumper, out ThreadJob stdErrPumper);
+
+ // LUCENENET: Note that ForkTest() creates the vstest.console.exe process.
+ // This spawns testhost.exe, which runs our test. We wait until
+ // the process starts and logs its own Id so we know who to kill later.
+ int processIdToKill = WaitForProcessToKillLogFile(tempProcessToKillFile.FullName);
+
+ // Setup a time to crash the forked thread
+ int crashTime = TestUtil.NextInt32(Random, 4000, 5000); // LUCENENET: Adjusted these up by 1 second to give our tests some more time to spin up
+ ThreadJob t = new ThreadAnonymousClass(this, crashTime, processIdToKill);
+
+ t.Priority = ThreadPriority.Highest;
+ t.Start();
+ t.Join(); // Wait for our thread to kill the other process
+
+ // if we succeeded in finding an index, we are done.
+ if (CheckIndexes(tempDir))
+ {
+ EndOutput(p, childOut, stdOutPumper, stdErrPumper);
+ return;
+ }
+ EndOutput(p, childOut, stdOutPumper, stdErrPumper);
+ }
+ }
+ else
+ {
+ // LUCENENET specific - suppressing the Lucene3x codec with[SuppressCodecs] at
+ // the top of this file so we can always run the fork.
+
+ // we are the fork, log our processId so the original test can kill us.
+ int processIdToKill = Process.GetCurrentProcess().Id;
+ string processIdToKillFile = SystemProperties.GetProperty("tests:tempProcessToKillFile");
+
+ assertNotNull("No tests:tempProcessToKillFile value was passed to the fork. This is a required system property.", processIdToKillFile);
+
+ // Writing this file will kick off the thread that crashes us.
+ using (var writer = new StreamWriter(processIdToKillFile, append: false, Encoding.UTF8, bufferSize: 32))
+ writer.WriteLine(processIdToKill.ToString(CultureInfo.InvariantCulture));
+
+ // run the test until we crash.
+ for (int i = 0; i < 100; i++)
+ {
+ base.TestNRTThreads_Mem();
+ }
+
+ }
+ }
+
+ private sealed class ThreadAnonymousClass : ThreadJob
+ {
+ private readonly TestIndexWriterOnJRECrash outerInstance;
+
+ private readonly int crashTime;
+ private readonly int processIdToKill;
+
+ public ThreadAnonymousClass(TestIndexWriterOnJRECrash outerInstance, int crashTime, int processIdToKill)
+ {
+ this.outerInstance = outerInstance;
+ this.crashTime = crashTime;
+ this.processIdToKill = processIdToKill;
+ }
+
+ public override void Run()
+ {
+ try
+ {
+ Thread.Sleep(crashTime);
+ }
+ catch (Exception e) when (e.IsInterruptedException())
+ {
+ }
+ // Time to crash
+ outerInstance.CrashDotNet(processIdToKill);
+ }
+ }
+
+ public Process ForkTest(string tempDir, string tempProcessToKillFile)
+ {
+ //get the full location of the assembly with DaoTests in it
+ string testAssemblyPath = Assembly.GetAssembly(typeof(TestIndexWriterOnJRECrash)).Location;
+
+ //get the folder that's in
+ string theDirectory = Path.GetDirectoryName(testAssemblyPath);
+ // Set up the process to run the console app
+ ProcessStartInfo startInfo = new ProcessStartInfo
+ {
+ FileName = "dotnet",
+ Arguments = string.Join(" ", new[] {
+ // LUCENENET NOTE: dotnet test doesn't need the --no-build flag since we are passing the DLL path in
+ "test", testAssemblyPath,
+ "--framework", GetTargetFramework(),
+ "--filter", nameof(TestIndexWriterOnJRECrash),
+ "--logger:\"console;verbosity=normal\"",
+ "--",
+ $"RunConfiguration.TargetPlatform={GetTargetPlatform()}",
+ // LUCENENET NOTE: Since in our CI environment we create a lucene.testSettings.config file
+ // for all tests, we need to pass some of these settings as test run parameters to override
+ // for this process. These are read as system properties on the inside of the application.
+ TestRunParameter("assert", "true"),
+ TestRunParameter("tests:seed", SeedUtils.FormatSeed(Random.NextInt64())),
+ TestRunParameter("tests:culture", Thread.CurrentThread.CurrentCulture.Name),
+ TestRunParameter("tests:crashmode", "true"),
+ // passing NIGHTLY to this test makes it run for much longer, easier to catch it in the act...
+ TestRunParameter("tests:nightly", "true"),
+ TestRunParameter("tempDir", tempDir),
+ // This file is for passing the process ID of the fork back to the original test so it can kill it.
+ TestRunParameter("tests:tempProcessToKillFile", tempProcessToKillFile),
+ }),
+ WorkingDirectory = theDirectory,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ };
+
+ Process p = Process.Start(startInfo);
+
+ //// LUCENENET: For debugging, it is helpful to do this sometimes
+ //var stdOut = p.StandardOutput.ReadToEnd();
+ //var stdErr = p.StandardError.ReadToEnd();
+
+ return p;
+ }
+
+ private static string TestRunParameter(string name, string value)
+ {
+ // See: https://github.com/microsoft/vstest/issues/862#issuecomment-621737720
+ return $"TestRunParameters.Parameter(name=\\\"{Escape(name)}\\\", value=\\\"{Escape(value)}\\\")";
+ }
+
+ private static string Escape(string value)
+ => value.Replace(Space, string.Concat(BackSlash, Space));
+
+ private const string BackSlash = "\\";
+ private const string Space = " ";
+
+ private TextWriter BeginOutput(Process p, out ThreadJob stdOutPumper, out ThreadJob stdErrPumper)
+ {
+ // We pump everything to stderr.
+ TextWriter childOut = Console.Error;
+ stdOutPumper = ThreadPumper.Start(p.StandardOutput, childOut);
+ stdErrPumper = ThreadPumper.Start(p.StandardError, childOut);
+ if (Verbose) childOut.WriteLine(">>> Begin subprocess output");
+ return childOut;
+ }
+
+ private void EndOutput(Process p, TextWriter childOut, ThreadJob stdOutPumper, ThreadJob stdErrPumper)
+ {
+ p.WaitForExit(10000);
+ stdOutPumper.Join();
+ stdErrPumper.Join();
+ if (Verbose) childOut.WriteLine("<<< End subprocess output");
+ }
+
+ private string GetTargetFramework()
+ {
+ var targetFrameworkAttribute = GetType().Assembly.GetAttributes(inherit: false).Where(a => a.Key == "TargetFramework").FirstOrDefault();
+ if (targetFrameworkAttribute is null)
+ Assert.Fail("TargetFramework metadata not found in this assembly.");
+ return targetFrameworkAttribute.Value;
+ }
+
+ private string GetTargetPlatform()
+ {
+ return Environment.Is64BitProcess ? "x64" : "x86";
+ }
+
+ ///
+ /// A pipe thread. It'd be nice to reuse guava's implementation for this...
+ internal static class ThreadPumper
+ {
+ public static ThreadJob Start(TextReader from, TextWriter to)
+ {
+ ThreadJob t = new ThreadPumperAnonymousClass(from, to);
+ t.Start();
+ return t;
+ }
+
+ private sealed class ThreadPumperAnonymousClass : ThreadJob
+ {
+ private TextReader from;
+ private TextWriter to;
+
+ public ThreadPumperAnonymousClass(TextReader from, TextWriter to)
+ {
+ this.from = from;
+ this.to = to;
+ }
+
+ public override void Run()
+ {
+ try
+ {
+ char[] buffer = new char[1024];
+ int len;
+ while ((len = from.Read(buffer, 0, buffer.Length)) > 0)
+ {
+ if (Verbose)
+ {
+ to.Write(buffer, 0, len);
+ }
+ }
+ }
+ catch (Exception e) when (e.IsIOException())
+ {
+ Console.Error.WriteLine("Couldn't pipe from the forked process: " + e.ToString());
+ }
+ }
+ }
+ }
+
+ ///
+ /// Recursively looks for indexes underneath file
,
+ /// and runs checkindex on them. returns true if it found any indexes.
+ ///
+ ///
+ /// LUCENENET: Since our base class will create an index, process it, then delete
+ /// it until it crashes, we don't expect there to be more than one index
+ /// when we get here. So, we return true on the first index found and
+ /// checked. There are a couple of cases where we don't check the index:
+ ///
+ /// - If the index has no _segments files.
+ /// - If GetLastCommitGeneration() == 1.
+ ///
+ /// In both of these cases, the index is considered valid, but
+ /// we skip the check because there is no way to check it for corruption.
+ /// This is why our test will try up to 10 times to get an index to check.
+ ///
+ /// When an index is corrupt, we will get an exception from
+ /// to fail the test.
+ ///
+ public virtual bool CheckIndexes(FileSystemInfo file)
+ {
+ if (file is DirectoryInfo directoryInfo)
+ {
+ using BaseDirectoryWrapper dir = NewFSDirectory(directoryInfo);
+ dir.CheckIndexOnDispose = false; // don't double-checkindex
+ if (DirectoryReader.IndexExists(dir))
+ {
+ if (Verbose)
+ {
+ Console.Error.WriteLine("Checking index: " + file);
+ }
+ // LUCENE-4738: if we crashed while writing first
+ // commit it's possible index will be corrupt (by
+ // design we don't try to be smart about this case
+ // since that too risky):
+ if (SegmentInfos.GetLastCommitGeneration(dir) > 1)
+ {
+ TestUtil.CheckIndex(dir);
+ }
+ return true;
+ }
+ dir.Dispose();
+ foreach (DirectoryInfo f in directoryInfo.EnumerateDirectories())
+ {
+ if (CheckIndexes(f))
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ // LUCENENET: Wait for our test to spin up and log its PID so we can kill it.
+ private int WaitForProcessToKillLogFile(string processToKillFile)
+ {
+ bool exists = false;
+ Thread.Sleep(500);
+ for (int i = 0; i < 150; i++)
+ {
+ if (File.Exists(processToKillFile))
+ {
+ exists = true;
+ break;
+ }
+ Thread.Sleep(200);
+ }
+ // If the fork didn't log its process id, it is a failure.
+ assertTrue("The test fork didn't log its process id, so we cannot kill it", exists);
+ using var reader = new StreamReader(processToKillFile, Encoding.UTF8);
+ // LUCENENET: Our file only has one line with the process Id in it
+ return int.Parse(reader.ReadLine().Trim(), CultureInfo.InvariantCulture);
+ }
+
+ public virtual void CrashDotNet(int processIdToKill)
+ {
+ Process process = null;
+ try
+ {
+ process = Process.GetProcessById(processIdToKill);
+ }
+ catch (ArgumentException)
+ {
+ // We get here if the process wasn't running for some reason.
+ // We should fix the forked test to make it run longer if we get here.
+ fail("The test completed before we could kill it.");
+ }
+#if FEATURE_PROCESS_KILL_ENTIREPROCESSTREE
+ process.Kill(entireProcessTree: true);
+#else
+ process.Kill();
+#endif
+ process.WaitForExit(10000);
+ // We couldn't get .NET to crash for some reason.
+ assertTrue(process.HasExited);
+ }
+ }
+}