refactor!: reworked the setup methods to handle stripped executables
This commit is contained in:
parent
a150a22022
commit
71c0d4a217
11 changed files with 270 additions and 60 deletions
28
src/MfGames.ToolBuilder/FakedRootCommand.cs
Normal file
28
src/MfGames.ToolBuilder/FakedRootCommand.cs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
using System;
|
||||||
|
using System.CommandLine;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace MfGames.ToolBuilder
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Fakes the functionality of the RootCommand because of the given issue:
|
||||||
|
/// https://github.com/dotnet/command-line-api/issues/1471
|
||||||
|
/// In short, RootCommand doesn't resolve properly when working in certain
|
||||||
|
/// situations, mainly having the command line parser in a library that is
|
||||||
|
/// called by another executable.
|
||||||
|
/// </summary>
|
||||||
|
public class FakedRootCommand : Command
|
||||||
|
{
|
||||||
|
public FakedRootCommand(ToolNames names)
|
||||||
|
: base(names.GetExecutableName(), string.Empty)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public string[] GetArguments()
|
||||||
|
{
|
||||||
|
string[] args = Environment.GetCommandLineArgs();
|
||||||
|
|
||||||
|
return args.Length == 0 ? args : args.Skip(1).ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,12 +46,12 @@ namespace MfGames.ToolBuilder.Globals
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
// If we don't have an internal name, blow up.
|
// If we don't have an internal name, blow up.
|
||||||
string? internalName = this.InternalName;
|
string? configName = this.Names?.GetConfigName();
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(internalName))
|
if (string.IsNullOrWhiteSpace(configName))
|
||||||
{
|
{
|
||||||
throw new ApplicationException(
|
throw new ApplicationException(
|
||||||
"Cannot determine the default configuration path unless internal name has been set.");
|
"Cannot determine the default configuration path unless the configuration name has been set.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Figure out the path to the default configuration. This is
|
// Figure out the path to the default configuration. This is
|
||||||
|
@ -61,7 +61,7 @@ namespace MfGames.ToolBuilder.Globals
|
||||||
.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||||
string appDirectory = Path.Combine(
|
string appDirectory = Path.Combine(
|
||||||
configDirectory,
|
configDirectory,
|
||||||
internalName);
|
configName);
|
||||||
string configPath = Path.Combine(appDirectory, "Settings.json");
|
string configPath = Path.Combine(appDirectory, "Settings.json");
|
||||||
|
|
||||||
return configPath;
|
return configPath;
|
||||||
|
@ -72,7 +72,7 @@ namespace MfGames.ToolBuilder.Globals
|
||||||
/// Gets or sets the name of the application. This is used to figure out
|
/// Gets or sets the name of the application. This is used to figure out
|
||||||
/// the name of the configuration file and what is shown on the screen.
|
/// the name of the configuration file and what is shown on the screen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? InternalName { get; set; }
|
public ToolNames? Names { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the common options to the command.
|
/// Adds the common options to the command.
|
||||||
|
@ -138,13 +138,13 @@ namespace MfGames.ToolBuilder.Globals
|
||||||
/// Sets the internal name of the application, used for the
|
/// Sets the internal name of the application, used for the
|
||||||
/// configuration path.
|
/// configuration path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="internalName">
|
/// <param name="names">
|
||||||
/// The internal name of the application.
|
/// The names object for the tool.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <returns>The service for chaining operations.</returns>
|
/// <returns>The service for chaining operations.</returns>
|
||||||
public ConfigToolGlobalService WithInternalName(string internalName)
|
public ConfigToolGlobalService WithNames(ToolNames? names)
|
||||||
{
|
{
|
||||||
this.InternalName = internalName;
|
this.Names = names;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -416,11 +416,12 @@ namespace MfGames.ToolBuilder.Tables
|
||||||
new Dictionary<CharMapPositions, char>
|
new Dictionary<CharMapPositions, char>
|
||||||
{
|
{
|
||||||
{ CharMapPositions.DividerY, ' ' },
|
{ CharMapPositions.DividerY, ' ' },
|
||||||
|
{ CharMapPositions.BottomCenter, ' ' },
|
||||||
})
|
})
|
||||||
.WithHeaderCharMapDefinition(
|
.WithHeaderCharMapDefinition(
|
||||||
new Dictionary<HeaderCharMapPositions, char>
|
new Dictionary<HeaderCharMapPositions, char>
|
||||||
{
|
{
|
||||||
{ HeaderCharMapPositions.BottomCenter, ' ' },
|
{ HeaderCharMapPositions.BottomCenter, '+' },
|
||||||
{ HeaderCharMapPositions.Divider, ' ' },
|
{ HeaderCharMapPositions.Divider, ' ' },
|
||||||
{ HeaderCharMapPositions.BorderBottom, '-' },
|
{ HeaderCharMapPositions.BorderBottom, '-' },
|
||||||
});
|
});
|
||||||
|
@ -455,7 +456,11 @@ namespace MfGames.ToolBuilder.Tables
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write out the results.
|
// Write out the results.
|
||||||
context.Console.Out.Write(builder.Export().ToString());
|
string rendered = builder.Export()
|
||||||
|
.ToString()
|
||||||
|
.TrimEnd(' ', '\n', '\r', '\0');
|
||||||
|
|
||||||
|
context.Console.Out.WriteLine(rendered);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,17 +30,17 @@ namespace MfGames.ToolBuilder
|
||||||
|
|
||||||
private readonly LoggingToolGlobalService loggingService;
|
private readonly LoggingToolGlobalService loggingService;
|
||||||
|
|
||||||
|
private readonly ToolNames names;
|
||||||
|
|
||||||
public ToolBuilder(
|
public ToolBuilder(
|
||||||
string applicationName,
|
ToolNames names,
|
||||||
string internalName,
|
|
||||||
string[] arguments)
|
string[] arguments)
|
||||||
{
|
{
|
||||||
// Create our various services.
|
// Create our various services.
|
||||||
|
this.names = names;
|
||||||
this.arguments = arguments;
|
this.arguments = arguments;
|
||||||
this.ApplicationName = applicationName;
|
|
||||||
this.InternalName = internalName;
|
|
||||||
this.configService = new ConfigToolGlobalService()
|
this.configService = new ConfigToolGlobalService()
|
||||||
.WithInternalName(this.InternalName);
|
.WithNames(this.names);
|
||||||
this.loggingService = new LoggingToolGlobalService();
|
this.loggingService = new LoggingToolGlobalService();
|
||||||
|
|
||||||
// Set up logging first so we can report the loading process. This
|
// Set up logging first so we can report the loading process. This
|
||||||
|
@ -76,22 +76,11 @@ namespace MfGames.ToolBuilder
|
||||||
this.ConfigureContainer);
|
this.ConfigureContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the human-readable name of the application.
|
|
||||||
/// </summary>
|
|
||||||
public string ApplicationName { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the internal name of the application.
|
|
||||||
/// </summary>
|
|
||||||
public string InternalName { get; }
|
|
||||||
|
|
||||||
public static ToolBuilder Create(
|
public static ToolBuilder Create(
|
||||||
string applicationName,
|
ToolNames names,
|
||||||
string internalName,
|
|
||||||
string[] arguments)
|
string[] arguments)
|
||||||
{
|
{
|
||||||
return new ToolBuilder(applicationName, internalName, arguments);
|
return new ToolBuilder(names, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ToolBuilder ConfigureContainer(
|
public ToolBuilder ConfigureContainer(
|
||||||
|
@ -155,6 +144,11 @@ namespace MfGames.ToolBuilder
|
||||||
AppDomain.CurrentDomain.ProcessExit +=
|
AppDomain.CurrentDomain.ProcessExit +=
|
||||||
(_, _) => Log.CloseAndFlush();
|
(_, _) => Log.CloseAndFlush();
|
||||||
|
|
||||||
|
// Register the names as a singleton instance.
|
||||||
|
builder.RegisterInstance(this.names)
|
||||||
|
.As<ToolNames>()
|
||||||
|
.SingleInstance();
|
||||||
|
|
||||||
// Register the global services as singletons.
|
// Register the global services as singletons.
|
||||||
builder
|
builder
|
||||||
.RegisterInstance(this.configService)
|
.RegisterInstance(this.configService)
|
||||||
|
@ -165,12 +159,6 @@ namespace MfGames.ToolBuilder
|
||||||
.AsSelf()
|
.AsSelf()
|
||||||
.SingleInstance();
|
.SingleInstance();
|
||||||
|
|
||||||
// Register the tool service since we have to use the factory to
|
|
||||||
// use it.
|
|
||||||
builder
|
|
||||||
.Register(this.CreateToolService)
|
|
||||||
.SingleInstance();
|
|
||||||
|
|
||||||
// Register the components required to make the CLI work.
|
// Register the components required to make the CLI work.
|
||||||
builder.RegisterModule<ToolBuilderModule>();
|
builder.RegisterModule<ToolBuilderModule>();
|
||||||
}
|
}
|
||||||
|
@ -180,14 +168,7 @@ namespace MfGames.ToolBuilder
|
||||||
IServiceCollection services)
|
IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddAutofac();
|
services.AddAutofac();
|
||||||
}
|
services.AddHostedService<ToolService>();
|
||||||
|
|
||||||
private ToolService CreateToolService(IComponentContext context)
|
|
||||||
{
|
|
||||||
var factory = context.Resolve<ToolService.Factory>();
|
|
||||||
var service = factory(this.InternalName);
|
|
||||||
|
|
||||||
return service;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ namespace MfGames.ToolBuilder
|
||||||
builder
|
builder
|
||||||
.RegisterAssemblyTypes(this.GetType().Assembly)
|
.RegisterAssemblyTypes(this.GetType().Assembly)
|
||||||
.Except<ToolService>()
|
.Except<ToolService>()
|
||||||
|
.Except<ToolNames>()
|
||||||
.Except<ConfigToolGlobalService>()
|
.Except<ConfigToolGlobalService>()
|
||||||
.Except<LoggingToolGlobalService>()
|
.Except<LoggingToolGlobalService>()
|
||||||
.AsSelf()
|
.AsSelf()
|
||||||
|
|
|
@ -60,7 +60,7 @@ namespace MfGames.ToolBuilder
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var messages = errors
|
IEnumerable<string> messages = errors
|
||||||
.SelectMany(x => x.Errors)
|
.SelectMany(x => x.Errors)
|
||||||
.Select(x => x.Message);
|
.Select(x => x.Message);
|
||||||
|
|
||||||
|
|
74
src/MfGames.ToolBuilder/ToolNames.cs
Normal file
74
src/MfGames.ToolBuilder/ToolNames.cs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace MfGames.ToolBuilder
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides the name of the tool in its various formats. This is used to
|
||||||
|
/// identify the executable name and other elements used throughout the
|
||||||
|
/// system.
|
||||||
|
/// </summary>
|
||||||
|
public class ToolNames
|
||||||
|
{
|
||||||
|
public ToolNames(string name)
|
||||||
|
{
|
||||||
|
this.Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||||
|
|
||||||
|
// The name of the executing program comes in as the first parameter.
|
||||||
|
string[] args = Environment.GetCommandLineArgs();
|
||||||
|
|
||||||
|
if (args.Length > 0)
|
||||||
|
{
|
||||||
|
this.ExecutableName = Path.GetFileNameWithoutExtension(args[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the configuration file. This will fall back to Name
|
||||||
|
/// if not provided.
|
||||||
|
/// </summary>
|
||||||
|
public string? ConfigName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the human-readable name of the application, falling back to Name
|
||||||
|
/// if not provided.
|
||||||
|
/// </summary>
|
||||||
|
public string? DisplayName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the executable. This will fall back to ConfigName
|
||||||
|
/// if this propery is null.
|
||||||
|
/// </summary>
|
||||||
|
public string? ExecutableName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the required name of the application. This can be human-readable
|
||||||
|
/// or not, depending on the developer.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the configuration name using any fallbacks.
|
||||||
|
/// </summary>
|
||||||
|
public string GetConfigName()
|
||||||
|
{
|
||||||
|
return this.ConfigName ?? this.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the display name using any fallbacks.
|
||||||
|
/// </summary>
|
||||||
|
public string GetDisplayName()
|
||||||
|
{
|
||||||
|
return this.DisplayName ?? this.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the executable name using any fallbacks.
|
||||||
|
/// </summary>
|
||||||
|
public string GetExecutableName()
|
||||||
|
{
|
||||||
|
return this.ExecutableName ?? this.GetConfigName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,8 +25,6 @@ namespace MfGames.ToolBuilder
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ToolService : IHostedService
|
public class ToolService : IHostedService
|
||||||
{
|
{
|
||||||
private readonly string cliName;
|
|
||||||
|
|
||||||
private readonly IList<ITopCommand> commands;
|
private readonly IList<ITopCommand> commands;
|
||||||
|
|
||||||
private readonly ConfigToolGlobalService configService;
|
private readonly ConfigToolGlobalService configService;
|
||||||
|
@ -37,16 +35,17 @@ namespace MfGames.ToolBuilder
|
||||||
|
|
||||||
private readonly LoggingToolGlobalService loggingService;
|
private readonly LoggingToolGlobalService loggingService;
|
||||||
|
|
||||||
|
private readonly ToolNames names;
|
||||||
|
|
||||||
public ToolService(
|
public ToolService(
|
||||||
string cliName,
|
ToolNames names,
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
IHostApplicationLifetime lifetime,
|
IHostApplicationLifetime lifetime,
|
||||||
IList<ITopCommand> commands,
|
IList<ITopCommand> commands,
|
||||||
ConfigToolGlobalService configService,
|
ConfigToolGlobalService configService,
|
||||||
LoggingToolGlobalService loggingService)
|
LoggingToolGlobalService loggingService)
|
||||||
{
|
{
|
||||||
this.cliName = cliName
|
this.names = names;
|
||||||
?? throw new ArgumentNullException(nameof(cliName));
|
|
||||||
this.lifetime = lifetime;
|
this.lifetime = lifetime;
|
||||||
this.commands = commands;
|
this.commands = commands;
|
||||||
this.configService = configService;
|
this.configService = configService;
|
||||||
|
@ -54,8 +53,6 @@ namespace MfGames.ToolBuilder
|
||||||
this.logger = logger.ForContext<ToolService>();
|
this.logger = logger.ForContext<ToolService>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public delegate ToolService Factory(string cliName);
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
@ -78,13 +75,13 @@ namespace MfGames.ToolBuilder
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Command CreateRootCommand()
|
private FakedRootCommand CreateRootCommand()
|
||||||
{
|
{
|
||||||
// Create the root command and add in the top-level commands
|
// Create the root command and add in the top-level commands
|
||||||
// underneath it. We can't use the "real" `RootCommand` here because
|
// underneath it. We can't use the "real" `RootCommand` here because
|
||||||
// it doesn't work in stripped executables (because this is a
|
// it doesn't work in stripped executables (because this is a
|
||||||
// library) so we fake it with a "normal" command.
|
// library) so we fake it with a "normal" command.
|
||||||
var root = new Command(this.cliName, string.Empty);
|
var root = new FakedRootCommand(this.names);
|
||||||
|
|
||||||
foreach (var command in this.commands)
|
foreach (var command in this.commands)
|
||||||
{
|
{
|
||||||
|
@ -117,6 +114,10 @@ namespace MfGames.ToolBuilder
|
||||||
this.logger.Fatal(
|
this.logger.Fatal(
|
||||||
exception,
|
exception,
|
||||||
"Unhandled exception!");
|
"Unhandled exception!");
|
||||||
|
|
||||||
|
Environment.ExitCode = Environment.ExitCode == 0
|
||||||
|
? 1
|
||||||
|
: Environment.ExitCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,20 +126,27 @@ namespace MfGames.ToolBuilder
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Build the command tree.
|
// Build the command tree.
|
||||||
Command root = this.CreateRootCommand();
|
FakedRootCommand root = this.CreateRootCommand();
|
||||||
string[] args = Environment.GetCommandLineArgs();
|
string[] args = root.GetArguments();
|
||||||
|
|
||||||
// Execute the command.
|
// Execute the command.
|
||||||
this.logger.Verbose(
|
this.logger.Verbose(
|
||||||
"Running the command-line arguments: {Arguments}",
|
"Running the command-line arguments: {Arguments}",
|
||||||
args);
|
args);
|
||||||
|
|
||||||
Environment.ExitCode = await new CommandLineBuilder(root)
|
CommandLineBuilder builder = new CommandLineBuilder(root)
|
||||||
.UseDefaults()
|
.UseDefaults()
|
||||||
.UseExceptionHandler(this.OnException)
|
.UseExceptionHandler(this.OnException);
|
||||||
.Build()
|
|
||||||
|
Parser cli = builder.Build();
|
||||||
|
int exitCode = await cli
|
||||||
.InvokeAsync(args)
|
.InvokeAsync(args)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (exitCode != 0)
|
||||||
|
{
|
||||||
|
Environment.ExitCode = exitCode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="CliWrap" Version="3.3.3" />
|
||||||
<PackageReference Include="coverlet.collector" Version="3.0.1">
|
<PackageReference Include="coverlet.collector" Version="3.0.1">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
|
113
tests/MfGames.ToolBuilder.Tests/SampleToolTests.cs
Normal file
113
tests/MfGames.ToolBuilder.Tests/SampleToolTests.cs
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using CliWrap;
|
||||||
|
using CliWrap.Exceptions;
|
||||||
|
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace MfGames.ToolBuilder.Tests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Tests the SampleTool in the tests directory to make sure the
|
||||||
|
/// basic functionality is correct.
|
||||||
|
/// </summary>
|
||||||
|
public class SampleToolTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task CrashCommandFails()
|
||||||
|
{
|
||||||
|
// Run the executable using CliWrap.
|
||||||
|
FileInfo projectFile = GetProjectFile();
|
||||||
|
StringBuilder output = new();
|
||||||
|
CancellationToken cancellationToken =
|
||||||
|
new CancellationTokenSource(TimeSpan.FromSeconds(20))
|
||||||
|
.Token;
|
||||||
|
|
||||||
|
var exception = Assert.ThrowsAsync<CommandExecutionException>(
|
||||||
|
async () => await Cli.Wrap("dotnet")
|
||||||
|
.WithArguments(
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
"run", "--project", projectFile.FullName, "--",
|
||||||
|
"crash",
|
||||||
|
})
|
||||||
|
.WithWorkingDirectory(projectFile.DirectoryName!)
|
||||||
|
.WithStandardOutputPipe(PipeTarget.ToStringBuilder(output))
|
||||||
|
.ExecuteAsync(cancellationToken)
|
||||||
|
.ConfigureAwait(false));
|
||||||
|
|
||||||
|
// Verify the return code.
|
||||||
|
Assert.NotNull(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task TableCommandWorks()
|
||||||
|
{
|
||||||
|
// Run the executable using CliWrap.
|
||||||
|
FileInfo projectFile = GetProjectFile();
|
||||||
|
StringBuilder output = new();
|
||||||
|
CancellationToken cancellationToken =
|
||||||
|
new CancellationTokenSource(TimeSpan.FromSeconds(20))
|
||||||
|
.Token;
|
||||||
|
|
||||||
|
CommandResult result = await Cli.Wrap("dotnet")
|
||||||
|
.WithArguments(
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
"run", "--project", projectFile.FullName, "--", "table",
|
||||||
|
})
|
||||||
|
.WithWorkingDirectory(projectFile.DirectoryName!)
|
||||||
|
.WithStandardOutputPipe(PipeTarget.ToStringBuilder(output))
|
||||||
|
.ExecuteAsync(cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
// Verify the return code.
|
||||||
|
Assert.Equal(0, result.ExitCode);
|
||||||
|
|
||||||
|
// Check the output.
|
||||||
|
Assert.Equal(
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
"DefaultString DefaultInt32",
|
||||||
|
"-------------+------------",
|
||||||
|
"Row 1 1",
|
||||||
|
"Row 2 10",
|
||||||
|
"Row 3 100",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
output.ToString().Split("\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the file object representing the sample tool's project.
|
||||||
|
/// </summary>
|
||||||
|
private static FileInfo GetProjectFile()
|
||||||
|
{
|
||||||
|
// Loop up until we find the directory that contains it.
|
||||||
|
var parent = new DirectoryInfo(Environment.CurrentDirectory);
|
||||||
|
|
||||||
|
while (parent?.GetDirectories("SampleTool").Length == 0)
|
||||||
|
{
|
||||||
|
parent = parent.Parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we got a null, we can't find it.
|
||||||
|
if (parent == null)
|
||||||
|
{
|
||||||
|
throw new DirectoryNotFoundException(
|
||||||
|
"Cannot find sample tool directory from "
|
||||||
|
+ Environment.CurrentDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the project file inside there.
|
||||||
|
DirectoryInfo directory = parent.GetDirectories("SampleTool")[0];
|
||||||
|
FileInfo file = directory.GetFiles("SampleTool.csproj")[0];
|
||||||
|
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,8 +12,7 @@ namespace SampleTool
|
||||||
{
|
{
|
||||||
return await ToolBuilder
|
return await ToolBuilder
|
||||||
.Create(
|
.Create(
|
||||||
"Sample Application",
|
new ToolNames("SampleApplication"),
|
||||||
"SampleApplication",
|
|
||||||
args)
|
args)
|
||||||
.ConfigureContainer(ConfigureContainer)
|
.ConfigureContainer(ConfigureContainer)
|
||||||
.RunAsync();
|
.RunAsync();
|
||||||
|
|
Reference in a new issue