diff --git a/.github/workflows/benchmark.yaml b/.github/workflows/benchmark.yaml index 6581387..c757574 100644 --- a/.github/workflows/benchmark.yaml +++ b/.github/workflows/benchmark.yaml @@ -17,7 +17,7 @@ jobs: with: submodules: recursive fetch-depth: 0 - - uses: actions/setup-dotnet@v1 + - uses: actions/setup-dotnet@v3 with: dotnet-version: '7.0.x' - name: Build Deps diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 47b9afb..e67006c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -21,7 +21,7 @@ jobs: fetch-depth: 0 # Required by LockCheck CI - name: Install Dotnet - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v3 with: dotnet-version: "7.0.x" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 8582012..b9129a7 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -34,7 +34,7 @@ jobs: echo Current Version: ${{ steps.get_version.outputs.version }} - name: Install Dotnet - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v3 with: dotnet-version: "7.0.x" diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e7da94..1901830 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## Release 1.2.0 +* Prevent hanging on process kill by @domsleee in https://github.com/domsleee/ForceOps/pull/23 +* Disable parallelization on tests + ## Release 1.2.0 * Add aot, and exe in release * Add "list" subcommand diff --git a/Directory.Build.props b/Directory.Build.props index 8671bbf..a48c60e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,7 +2,7 @@ By hook or by crook, perform operations on files and directories. If they are in use by a process, kill the process. - 1.2.0 + 1.2.1 https://github.com/domsleee/forceops https://github.com/domsleee/forceops git diff --git a/ForceOps.Test/AssemblyInfo.cs b/ForceOps.Test/AssemblyInfo.cs new file mode 100644 index 0000000..c7fc3b1 --- /dev/null +++ b/ForceOps.Test/AssemblyInfo.cs @@ -0,0 +1 @@ +[assembly: CollectionBehavior(DisableTestParallelization = true)] diff --git a/ForceOps.Test/src/ListFileOrDirectoryLocksTest.cs b/ForceOps.Test/src/ListFileOrDirectoryLocksTest.cs index 1c025eb..011519d 100644 --- a/ForceOps.Test/src/ListFileOrDirectoryLocksTest.cs +++ b/ForceOps.Test/src/ListFileOrDirectoryLocksTest.cs @@ -14,11 +14,11 @@ public class ListFileOrDirectoryLocksTest : IDisposable public void WorksForDirectory() { var testContext = new TestContext(); - using var launchedProcess = LaunchProcessInDirectory(tempDirectoryPath); + using var launchedProcess = LaunchPowershellWithCommand(workingDirectory: tempDirectoryPath); new ListFileOrDirectoryLocks(testContext.forceOpsContext).PrintLocks(tempDirectoryPath); - var stdoutString = stdoutStringBuilder.ToString(); - Assert.Equal($"ProcessId,ExecutableName,ApplicationName\r\n{launchedProcess.process.Id},powershell.exe,powershell.exe\r\n", stdoutString); + var stdoutString = GetStdoutString(stdoutStringBuilder); + Assert.Contains($"ProcessId,ExecutableName,ApplicationName\r\n{launchedProcess.process.Id},powershell.exe,powershell.exe\r\n", stdoutString); } [Fact] @@ -29,7 +29,7 @@ public void WorksForFile() using var launchedProcess = HoldLockOnFileUsingPowershell(tempFilePath); new ListFileOrDirectoryLocks(testContext.forceOpsContext).PrintLocks(tempFilePath); - var stdoutString = stdoutStringBuilder.ToString(); + var stdoutString = GetStdoutString(stdoutStringBuilder); Assert.Equal($"ProcessId,ExecutableName,ApplicationName\r\n{launchedProcess.process.Id},powershell.exe,powershell.exe\r\n", stdoutString); } diff --git a/ForceOps.Test/src/ProgramTest.cs b/ForceOps.Test/src/ProgramTest.cs index ccfb3da..5b9ae6b 100644 --- a/ForceOps.Test/src/ProgramTest.cs +++ b/ForceOps.Test/src/ProgramTest.cs @@ -65,7 +65,8 @@ public void RelaunchedProgramWorks() string exeNameOverride = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "forceops.exe"); testContext.forceOpsContext.relaunchAsElevated = new RelaunchAsElevated() { verb = "", exeNameOverride = exeNameOverride }; var forceOps = new ForceOps(new[] { "delete", tempDirectoryPath }, testContext.forceOpsContext); - Assert.True(0 == forceOps.Run(), forceOps.caughtException?.ToString() + testContext.fakeLoggerFactory.GetAllLogsString() + stdoutStringBuilder.ToString()); + var stdoutString = GetStdoutString(stdoutStringBuilder); + Assert.True(0 == forceOps.Run(), forceOps.caughtException?.ToString() + testContext.fakeLoggerFactory.GetAllLogsString() + stdoutString); Assert.True(!Directory.Exists(tempDirectoryPath), "Deleted by relaunch"); Assert.Contains("Unable to perform operation as an unelevated process. Retrying as elevated and logging to", testContext.fakeLoggerFactory.GetAllLogsString()); } @@ -108,7 +109,6 @@ public void ListNonExistingFileThrowsMessage() Assert.Equal(@"Cannot list locks of 'C:\C:\C:\'. No such file or directory", testContext.friendlyExitMessage); } - public ProgramTest() { tempDirectoryPath = GetTemporaryFileName(); diff --git a/ForceOps.Test/src/TestUtil.cs b/ForceOps.Test/src/TestUtil.cs index e425409..bc9c64b 100644 --- a/ForceOps.Test/src/TestUtil.cs +++ b/ForceOps.Test/src/TestUtil.cs @@ -31,15 +31,42 @@ public static WrappedProcess LaunchPowershellWithCommand(string command = "", st } }; - List output = new(); - List error = new(); + StartProcessUntilProcessHasBeenLoadedMessage(process); + + return new WrappedProcess(process); + } + + public static WrappedProcess LaunchCmdWithCommand(string command = "", string workingDirectory = "") + { + var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "cmd", + WorkingDirectory = workingDirectory, + Arguments = $"/c \"{command}; echo process has been loaded && timeout /t 10 /nobreak\"", + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + } + }; + + StartProcessUntilProcessHasBeenLoadedMessage(process); + + return new WrappedProcess(process); + } + + static void StartProcessUntilProcessHasBeenLoadedMessage(Process process) + { + string output = ""; + string error = ""; process.OutputDataReceived += (sender, e) => { - if (e.Data != null) output.Add(e.Data); + if (e.Data != null) output += $"{e.Data}{System.Environment.NewLine}"; }; process.ErrorDataReceived += (sender, e) => { - if (e.Data != null) error.Add(e.Data); + if (e.Data != null) error += $"{e.Data}{System.Environment.NewLine}"; }; process.Start(); @@ -48,21 +75,19 @@ public static WrappedProcess LaunchPowershellWithCommand(string command = "", st var startTime = DateTime.Now; - while (!(output.LastOrDefault() ?? "").EndsWith("process has been loaded") && !process.HasExited) + while (!output.Contains("process has been loaded") && !process.HasExited) { Thread.Sleep(50); if (DateTime.Now.Subtract(startTime).TotalSeconds > 5) { - throw new Exception("Gave up after waiting 5 seconds"); + throw new Exception($"Gave up after waiting 5 seconds.\nOutput: {output}\nError: {error}"); } } if (process.HasExited) { - throw new Exception($"Process has exited unexpectedly.\nOutput: {string.Join("\n", output)}\nError: {string.Join("\n", error)}"); + throw new Exception($"Process has exited unexpectedly.\nOutput: {output}\nError: {error}"); } - - return new WrappedProcess(process); } public static string GetTemporaryFileName() @@ -70,6 +95,12 @@ public static string GetTemporaryFileName() return Path.Join(Path.GetTempPath(), Guid.NewGuid().ToString()); } + public static string GetStdoutString(StringBuilder stdoutStringBuilder) + { + Console.Out.Flush(); + return stdoutStringBuilder.ToString(); + } + public static IDisposable CreateTemporaryDirectory(string directory) { Directory.CreateDirectory(directory); @@ -86,11 +117,13 @@ public static IDisposable CreateTemporaryDirectory(string directory) public static IDisposable RedirectStdout(StringBuilder stringBuilder) { var originalConsoleOut = Console.Out; + Console.Out.Flush(); Console.SetOut(new StringWriter(stringBuilder)); return Disposable.Create(() => { Console.SetOut(originalConsoleOut); + Console.Out.Flush(); }); } }