Skip to content

Commit

Permalink
fix: cached csx not throwing error with non zero exit code #49
Browse files Browse the repository at this point in the history
  • Loading branch information
alirezanet committed Aug 1, 2022
1 parent f3653ae commit 9d5f252
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 13 deletions.
1 change: 0 additions & 1 deletion src/Husky/Cli/CommandBase.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using CliFx;
using CliFx.Attributes;
using CliFx.Exceptions;
using CliFx.Infrastructure;
Expand Down
25 changes: 20 additions & 5 deletions src/Husky/Cli/ExecCommand.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Collections.Immutable;
using System.IO.Abstractions;
using System.Reflection;
using System.Security.Cryptography;
using CliFx.Attributes;
using CliFx.Exceptions;
Expand All @@ -19,11 +18,13 @@ public class ExecCommand : CommandBase
{
private readonly IFileSystem _fileSystem;
private readonly IGit _git;
private readonly IAssembly _assembly;

public ExecCommand(IFileSystem fileSystem, IGit git)
public ExecCommand(IFileSystem fileSystem, IGit git, IAssembly assembly)
{
_fileSystem = fileSystem;
_git = git;
_assembly = assembly;
}

[CommandParameter(0, Description = "The script file to execute")]
Expand Down Expand Up @@ -97,8 +98,9 @@ private async Task GenerateScriptCache(string scriptPath, string compiledScriptP

internal async Task ExecuteCachedScript(string scriptPath)
{

var gitPath = await _git.GetGitPathAsync();
var assembly = Assembly.LoadFile(_fileSystem.Path.Combine(gitPath, scriptPath));
var assembly = _assembly.LoadFile(_fileSystem.Path.Combine(gitPath, scriptPath));
var type = assembly.GetType("Submission#0");
if (type == null)
{
Expand All @@ -117,7 +119,20 @@ internal async Task ExecuteCachedScript(string scriptPath)
if (factory.Invoke(null, new object[] { submissionArray }) is Task<object> task)
try
{
await task;
var result = await task;
switch (result)
{
case null or 0:
return;
case int i:
throw new CommandException("script execution failed", i);
default:
throw new CommandException("script execution failed");
}
}
catch (CommandException)
{
throw;
}
catch (Exception e)
{
Expand All @@ -142,7 +157,7 @@ internal async ValueTask ExecuteScript(string scriptPath)
internal async Task<(bool, string)> GetCachedScript(string scriptPath)
{
var cacheFolder = await GetHuskyCacheFolder();
await using var fileStream = new FileStream(scriptPath, FileMode.Open);
await using var fileStream = _fileSystem.FileStream.Create(scriptPath, FileMode.Open);

var hash = await CalculateHashAsync(fileStream);

Expand Down
2 changes: 1 addition & 1 deletion src/Husky/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ ServiceProvider BuildServiceProvider()
.AddSingleton<IGit, Git>()
.AddSingleton<IHuskyTaskLoader, HuskyTaskLoader>()
.AddSingleton<IArgumentParser, ArgumentParser>()

.AddTransient<IExecutableTaskFactory, ExecutableTaskFactory>()
.AddTransient<IFileSystem, FileSystem>()
.AddTransient<IXmlIO, XmlIO>()
.AddTransient<IAssembly, AssemblyProxy>()
.AddTransient<ICliWrap, HuskyCliWrap>()

// Commands
Expand Down
12 changes: 12 additions & 0 deletions src/Husky/Services/AssemblyProxy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Reflection;
using Husky.Services.Contracts;

namespace Husky.Services;

public class AssemblyProxy : IAssembly
{
public Assembly LoadFile(string path)
{
return Assembly.LoadFile(path);
}
}
8 changes: 8 additions & 0 deletions src/Husky/Services/Contracts/IAssembly.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Reflection;

namespace Husky.Services.Contracts;

public interface IAssembly
{
public Assembly LoadFile(string path);
}
55 changes: 49 additions & 6 deletions tests/HuskyTest/Cli/ExecCommandTests.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
using System.Collections.Immutable;
using System.IO.Abstractions;
using System.Reflection;
using System.Text;
using CliFx.Exceptions;
using CliFx.Infrastructure;
using FluentAssertions;
using Husky.Cli;
using Husky.Services.Contracts;
using Husky.Stdout;
using Husky.Utils;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
using NSubstitute;
using Xunit;

Expand All @@ -15,6 +22,7 @@ public class ExecCommandTests
private readonly FakeInMemoryConsole _console;
private readonly IFileSystem _io;
private readonly IGit _git;
private readonly IAssembly _assembly;

public ExecCommandTests()
{
Expand All @@ -24,14 +32,15 @@ public ExecCommandTests()
// sub
_io = Substitute.For<IFileSystem>();
_git = Substitute.For<IGit>();
_assembly = Substitute.For<IAssembly>();
}

[Fact]
public async Task Exec_WithoutCache_WhenFileMissing_ThrowException()
{
// Arrange
const string filePath = "fake_file.csx";
var command = new ExecCommand(_io, _git) { Path = filePath, NoCache = true };
var command = new ExecCommand(_io, _git, _assembly) { Path = filePath, NoCache = true };

// Act
Func<Task> act = async () => await command.ExecuteAsync(_console);
Expand All @@ -48,7 +57,7 @@ public async Task Exec_WithoutCache_WithErrorInScript_ThrowException()
_io.File.Exists(Arg.Any<string>()).Returns(true);
_io.Path.GetDirectoryName(Arg.Any<string>()).Returns(Directory.GetCurrentDirectory());
_io.File.ReadAllTextAsync(Arg.Any<string>()).Returns(stringContent);
var command = new ExecCommand(_io, _git) { Path = filePath, NoCache = true };
var command = new ExecCommand(_io, _git, _assembly) { Path = filePath, NoCache = true };

// Act
Func<Task> act = async () => await command.ExecuteAsync(_console);
Expand All @@ -68,7 +77,7 @@ public async Task Exec_WithoutCache_WithScriptThrowException_ThrowException()
_io.Path.GetDirectoryName(Arg.Any<string>()).Returns(Directory.GetCurrentDirectory());
_io.File.Exists(Arg.Any<string>()).Returns(true);
_io.File.ReadAllTextAsync(Arg.Any<string>()).Returns(stringContent);
var command = new ExecCommand(_io, _git) { Path = filePath, NoCache = true };
var command = new ExecCommand(_io, _git, _assembly) { Path = filePath, NoCache = true };

// Act
Func<Task> act = async () => await command.ExecuteAsync(_console);
Expand All @@ -87,7 +96,7 @@ public async Task Exec_WithoutCache_WithScriptReturnGreaterThan0_ThrowException(
_io.Path.GetDirectoryName(Arg.Any<string>()).Returns(Directory.GetCurrentDirectory());
_io.File.Exists(Arg.Any<string>()).Returns(true);
_io.File.ReadAllTextAsync(Arg.Any<string>()).Returns(stringContent);
var command = new ExecCommand(_io, _git) { Path = filePath, NoCache = true };
var command = new ExecCommand(_io, _git, _assembly) { Path = filePath, NoCache = true };

// Act
Func<Task> act = async () => await command.ExecuteAsync(_console);
Expand All @@ -106,7 +115,7 @@ public async Task Exec_WithoutCache_WithoutArguments_Succeed()
_io.Path.GetDirectoryName(Arg.Any<string>()).Returns(Directory.GetCurrentDirectory());
_io.File.Exists(Arg.Any<string>()).Returns(true);
_io.File.ReadAllTextAsync(Arg.Any<string>()).Returns(stringContent);
var command = new ExecCommand(_io, _git) { Path = filePath, NoCache = true };
var command = new ExecCommand(_io, _git, _assembly) { Path = filePath, NoCache = true };

// Act
await command.ExecuteAsync(_console);
Expand All @@ -123,10 +132,44 @@ public async Task Exec_WithoutCache_WithArguments_Succeed()
_io.Path.GetDirectoryName(Arg.Any<string>()).Returns(Directory.GetCurrentDirectory());
_io.File.Exists(Arg.Any<string>()).Returns(true);
_io.File.ReadAllTextAsync(Arg.Any<string>()).Returns(stringContent);
var command = new ExecCommand(_io, _git) { Path = filePath, Arguments = new List<string> { "test" }, NoCache = true };
var command = new ExecCommand(_io, _git, _assembly) { Path = filePath, Arguments = new List<string> { "test" }, NoCache = true };

// Act
await command.ExecuteAsync(_console);
}

[Fact]
public async Task Exec_CachedScript_ShouldHandleNonZeroExitCode()
{
// Arrange
const string filePath = "fake_file.csx";
const string stringContent = @"
return 1;
";
_io.Path.GetDirectoryName(Arg.Any<string>()).Returns(Directory.GetCurrentDirectory());
_io.File.Exists(Arg.Any<string>()).Returns(true);
_io.Directory.Exists(Arg.Any<string>()).Returns(true);
_io.File.ReadAllTextAsync(Arg.Any<string>()).Returns(stringContent);
var stream = new MemoryStream(Encoding.UTF8.GetBytes(stringContent));
_io.FileStream.Create(Arg.Any<string>(), FileMode.Open).Returns(stream);

var opts = ScriptOptions.Default
.WithSourceResolver(new SourceFileResolver(ImmutableArray<string>.Empty, null))
.WithImports("System", "System.IO", "System.Collections.Generic", "System.Text", "System.Threading.Tasks");
var script = CSharpScript.Create(stringContent.Trim(), opts, typeof(Globals));
var compilation = script.GetCompilation();

await using var assemblyStream = new MemoryStream();
var result = compilation.Emit(assemblyStream);
_assembly.LoadFile(Arg.Any<string>()).Returns(Assembly.Load(assemblyStream.ToArray()));

var command = new ExecCommand(_io, _git, _assembly) { Path = filePath, Arguments = new List<string> { "test" }, NoCache = false };

// Act
Func<Task> act = async () => await command.ExecuteAsync(_console);

// Assert
await act.Should().ThrowAsync<CommandException>().WithMessage("script execution failed");
}
}
}

0 comments on commit 9d5f252

Please sign in to comment.