Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .github/workflows/dotnet-format.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: "Dotnet Format"

# Controls when the action will run.
on:
schedule:
# Weekly At 19:00 on Monday. - 5am Australian/Brisbane time
- cron: '0 19 * * 1'
pull_request:
types: [opened, synchronize, reopened]
workflow_dispatch:
# Allows you to run this workflow manually from the Actions tab

jobs:
build:
name: "Format code and Create Pull Request if any changes"
runs-on: ubuntu-latest
permissions:
contents: write # Read Required to check out code, Write to create Git Tags
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x

- name: Run dotnet format
run: dotnet format --verbosity normal
working-directory: ./source

- name: Check for changes
id: detect_changes
run: |
set +e
git diff --quiet

if [ "$?" -eq 0 ]; then
echo "No changes detected."
else
echo "Changes detected."
echo "changes_detected=true" >> "$GITHUB_OUTPUT"
fi

- name: Create Pull Request if Changes Detected
if: steps.detect_changes.outputs.changes_detected == 'true'
env:
GH_TOKEN: ${{ secrets.RENOVATE_GITHUB_TOKEN }}
GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: |
git config user.email "bob@octopus.com"
git config user.name "GitHub Actions"

dateTimeStamp="$(date +'%Y%m%d%H%M%S')"
branchName="dotnet-format/$dateTimeStamp"

git checkout -b "$branchName"
git add -A ./source
git commit -m "Run dotnet format"
git push -f --set-upstream origin "$branchName"

# Always target PR's at the default branch
targetBranchName="${{ github.event.repository.default_branch }}"
gh pr create -t "$branchName" --head "$branchName" --base "$targetBranchName" --body "This PR was created automatically by the dotnet-format workflow."

2 changes: 1 addition & 1 deletion source/Shellfish/CallbackOutputTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public static partial class ShellCommandExtensionMethods
{
public static ShellCommand WithStdOutTarget(this ShellCommand shellCommand, Action<string> callback)
=> shellCommand.WithStdOutTarget(new CallbackOutputTarget(callback));

public static ShellCommand WithStdErrTarget(this ShellCommand shellCommand, Action<string> callback)
=> shellCommand.WithStdErrTarget(new CallbackOutputTarget(callback));
}
20 changes: 10 additions & 10 deletions source/Shellfish/InputQueue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,17 @@ async Task BeginMessagePump()
switch (notification.Type)
{
case NotificationType.Empty: // wait for the next wakeup signal
{
TaskCompletionSource<bool> sig;
lock (queue)
{
sig = wakeupSignal;
TaskCompletionSource<bool> sig;
lock (queue)
{
sig = wakeupSignal;
}

await sig.Task;
continue; // go round again
}

await sig.Task;
continue; // go round again
}

case NotificationType.Next when notification.Line is not null: // onNext
await processStdInput.WriteLineAsync(notification.Line);
await processStdInput.FlushAsync();
Expand All @@ -90,8 +90,8 @@ async Task BeginMessagePump()
processStdInput.Close();
return; // exit the entire message pump

// Normally we would have a default: case which logged or threw an "Unhandled case" exception,
// but we are a background task, there's nobody to observe such a thing.
// Normally we would have a default: case which logged or threw an "Unhandled case" exception,
// but we are a background task, there's nobody to observe such a thing.
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions source/Shellfish/PasteArguments.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ static class PasteArguments
internal static string JoinArguments(IEnumerable<string> arguments)
{
var stringBuilder = new StringBuilder();
foreach(var argument in arguments)
foreach (var argument in arguments)
{
AppendArgument(stringBuilder, argument);
}

return stringBuilder.ToString();
}

internal static void AppendArgument(StringBuilder stringBuilder, string argument)
{
if (stringBuilder.Length != 0)
Expand Down
16 changes: 8 additions & 8 deletions source/Shellfish/ShellCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -210,15 +210,15 @@ public string ToString(bool includeArguments)
switch (arguments)
{
case ShellCommandArguments.StringType s:
{
var argumentsAsString = includeArguments ? s.Value : "<arguments>";
return $"{executable} {argumentsAsString}";
}
{
var argumentsAsString = includeArguments ? s.Value : "<arguments>";
return $"{executable} {argumentsAsString}";
}
case ShellCommandArguments.ArgumentListType { Values.Length: > 0 } l:
{
var argumentsAsString = includeArguments ? PasteArguments.JoinArguments(l.Values) : $"<{l.Values.Length} arguments>";
return $"{executable} {argumentsAsString}";
}
{
var argumentsAsString = includeArguments ? PasteArguments.JoinArguments(l.Values) : $"<{l.Values.Length} arguments>";
return $"{executable} {argumentsAsString}";
}

default:
return executable;
Expand Down
6 changes: 3 additions & 3 deletions source/Shellfish/ShellCommandArguments.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ abstract class ShellCommandArguments
public static NoArgumentsType None { get; } = new();
public static StringType String(string value) => new(value);
public static ArgumentListType List(string[] value) => new(value);

// Don't construct this type directly, use ShellCommandArguments.None
public class NoArgumentsType : ShellCommandArguments;

// Don't construct this type directly, use ShellCommandArguments.String(value)
public class StringType(string value) : ShellCommandArguments
{
public string Value { get; } = value;
}

// Don't construct this type directly, use ShellCommandArguments.List(value)
public class ArgumentListType(string[] values) : ShellCommandArguments
{
Expand Down
2 changes: 1 addition & 1 deletion source/Shellfish/ShellCommandOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public enum ShellCommandOptions
/// Default value, equivalent to not specifying any options.
/// </summary>
None = 0,

/// <summary>
/// By default, if the CancellationToken is cancelled, the running process will be killed, and an OperationCanceledException
/// will be thrown, like the vast majority of other .NET code.
Expand Down
6 changes: 3 additions & 3 deletions source/Shellfish/ShellfishProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ public int SafelyGetExitCode()
}
}

// Common code for Execute and ExecuteAsync to handle stdin and stdout streaming
// Common code for Execute and ExecuteAsync to handle stdin and stdout streaming
void BeginIoStreams()
{
if (stdOutRedirected) process.BeginOutputReadLine();
Expand All @@ -180,8 +180,8 @@ void ConfigureArguments(ShellCommandArguments arguments)
process.StartInfo.Arguments = PasteArguments.JoinArguments(l.Values);
#endif
break;
// Deliberately no default case here: ShellCommandArguments.NoArgumentsType and Empty list are no-ops

// Deliberately no default case here: ShellCommandArguments.NoArgumentsType and Empty list are no-ops
}
}

Expand Down
2 changes: 1 addition & 1 deletion source/Shellfish/StringBuilderOutputTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public static partial class ShellCommandExtensionMethods
{
public static ShellCommand WithStdOutTarget(this ShellCommand shellCommand, StringBuilder stringBuilder)
=> shellCommand.WithStdOutTarget(new StringBuilderOutputTarget(stringBuilder));

public static ShellCommand WithStdErrTarget(this ShellCommand shellCommand, StringBuilder stringBuilder)
=> shellCommand.WithStdErrTarget(new StringBuilderOutputTarget(stringBuilder));
}
2 changes: 1 addition & 1 deletion source/Shellfish/Windows/AccessToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ static SafeAccessTokenHandle LogonUser(string username,
Interop.Advapi32.LogonType logonType,
Interop.Advapi32.LogonProvider logonProvider)
{
if(!Interop.Advapi32.LogonUser(username, domain, password, logonType, logonProvider, out var handle))
if (!Interop.Advapi32.LogonUser(username, domain, password, logonType, logonProvider, out var handle))
throw new Win32Exception();

return handle;
Expand Down
4 changes: 2 additions & 2 deletions source/Shellfish/Windows/Interop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ internal static extern bool GetCPInfoEx([MarshalAs(UnmanagedType.U4)] int codePa
[MarshalAs(UnmanagedType.U4)]
int dwFlags,
out CpInfoEx lpCPInfoEx);

const int MAX_DEFAULTCHAR = 2;
const int MAX_LEADBYTES = 12;
const int MAX_PATH = 260;
Expand Down Expand Up @@ -105,7 +105,7 @@ internal static class Userenv
// See https://msdn.microsoft.com/en-us/library/windows/desktop/bb762274(v=vs.85).aspx
[DllImport(Libraries.Userenv, SetLastError = true)]
internal static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment);

// See https://msdn.microsoft.com/en-us/library/windows/desktop/bb762281(v=vs.85).aspx
[DllImport(Libraries.Userenv, SetLastError = true)]
internal static extern bool LoadUserProfile(SafeAccessTokenHandle hToken, ref ProfileInfo lpProfileInfo);
Expand Down
2 changes: 1 addition & 1 deletion source/Shellfish/Windows/WindowStationAndDesktopAccess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public static void GrantAccessToWindowStationAndDesktop(string username, string?
var hWindowStation = GetProcessWindowStation();
const int windowStationAllAccess = 0x000f037f;
GrantAccess(username, domainName, hWindowStation, windowStationAllAccess);

var hDesktop = GetThreadDesktop();
const int desktopRightsAllAccess = 0x000f01ff;
GrantAccess(username, domainName, hDesktop, desktopRightsAllAccess);
Expand Down
6 changes: 3 additions & 3 deletions source/Tests/Plumbing/WindowsFactAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ public sealed class WindowsFactAttribute : FactAttribute
{
public WindowsFactAttribute()
{
if(!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) Skip = $"This test only runs on Windows";
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) Skip = $"This test only runs on Windows";
}
}

[AttributeUsage(AttributeTargets.Method)]
public sealed class WindowsTheoryAttribute : TheoryAttribute
{
public WindowsTheoryAttribute()
{
if(!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) Skip = $"This test only runs on Windows";
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) Skip = $"This test only runs on Windows";
}
}
}
2 changes: 1 addition & 1 deletion source/Tests/Plumbing/WindowsUserClassFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
public class WindowsUserClassFixture
{
static readonly object Gate = new();

const string Username = "test-shellexecutor";

internal TestUserPrincipal User { get; }
Expand Down
20 changes: 10 additions & 10 deletions source/Tests/ShellCommandFixture.StdInput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class ShellCommandFixtureStdInput
{
readonly CancellationTokenSource cancellationTokenSource = new(ShellCommandFixture.TestTimeout);
CancellationToken CancellationToken => cancellationTokenSource.Token;

[Theory, InlineData(SyncBehaviour.Sync), InlineData(SyncBehaviour.Async)]
public async Task ShouldWork(SyncBehaviour behaviour)
{
Expand Down Expand Up @@ -71,7 +71,7 @@ read lastname

var stdOut = new StringBuilder();
var stdErr = new StringBuilder();

// it's going to ask us for the names, we need to answer back or the process will stall forever; we can preload this
var stdIn = new TestInputSource();

Expand All @@ -94,7 +94,7 @@ read lastname
stdErr.ToString().Should().BeEmpty("no messages should be written to stderr");
stdOut.ToString().Should().Be("Enter First Name:" + Environment.NewLine + "Enter Last Name:" + Environment.NewLine + "Hello 'Bob' 'Octopus'" + Environment.NewLine);
}

[Theory, InlineData(SyncBehaviour.Sync), InlineData(SyncBehaviour.Async)]
public async Task ClosingStdInEarly(SyncBehaviour behaviour)
{
Expand All @@ -117,7 +117,7 @@ read lastname

var stdOut = new StringBuilder();
var stdErr = new StringBuilder();

// it's going to ask us for the names, we need to answer back or the process will stall forever; we can preload this
var stdIn = new TestInputSource();

Expand All @@ -141,7 +141,7 @@ read lastname
// When we close stdin the waiting process receives an EOF; Our trivial shell script interprets this as an empty string
stdOut.ToString().Should().Be("Enter First Name:" + Environment.NewLine + "Enter Last Name:" + Environment.NewLine + "Hello 'Bob' ''" + Environment.NewLine);
}

[Theory, InlineData(SyncBehaviour.Sync), InlineData(SyncBehaviour.Async)]
public async Task ShouldReleaseInputSourceWhenProgramExits(SyncBehaviour behaviour)
{
Expand Down Expand Up @@ -171,22 +171,22 @@ read firstname
.WithStdOutTarget(l =>
{
stdIn.Subscriber.Should().NotBeNull("the shellcommand should still be subscribed to the input source while the process is running");

// when we receive the first prompt, cancel and kill the process
if (l.Contains("Enter First Name:")) stdIn.OnNext("Bob");
})
.WithStdErrTarget(stdErr);

stdIn.Subscriber.Should().BeNull("the shellcommand should not subscribe to the input source until the process starts");

var result = behaviour == SyncBehaviour.Async
? await executor.ExecuteAsync(CancellationToken)
: executor.Execute(CancellationToken);

result.ExitCode.Should().Be(0, "the process should have run to completion");
stdErr.ToString().Should().BeEmpty("no messages should be written to stderr");
stdOut.ToString().Should().Be("Enter First Name:" + Environment.NewLine + "Hello 'Bob'" + Environment.NewLine);

stdIn.Subscriber.Should().BeNull("the shellcommand should have unsubscribed from the input source after the process exits");
}

Expand Down Expand Up @@ -294,7 +294,7 @@ read name
"Enter Name:" + Environment.NewLine + "Hello ''" + Environment.NewLine,
], because: "When we cancel the process we close StdIn and it shuts down. The process observes the EOF as empty string and prints 'Hello ' but there is a benign race condition which means we may not observe this output. Test needs to handle both cases");
}

// If someone wants to have an interactive back-and-forth with a process, they
// can use a type like this to do it. We don't want to quite commit to putting it
// in the public API though until we have a stronger use-case for it.
Expand All @@ -314,7 +314,7 @@ public void OnNext(string line)
{
Subscriber?.OnNext(line);
}

public void OnCompleted()
{
Subscriber?.OnCompleted();
Expand Down
2 changes: 1 addition & 1 deletion source/Tests/ShellCommandFixture.Windows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ static ShellCommandFixtureWindows()
#endif

readonly TestUserPrincipal user = fx.User;

// If unspecified, ShellCommand will default to the current directory, which our temporary user may not have access to.
// Our tests that run as a different user need to set a different working directory or they may fail.
readonly string commonAppDataPath = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
Expand Down
Loading
Loading