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); + } + } +}