Skip to content

Commit 977c05e

Browse files
authored
Add support for response files (#2320)
* Add support for response files I make workaround here for now, but essentially issues in CommadLine library. Also place workardoud for stupid bug with quoted argumetns with space in them. I notice issue with parsing and request for RSP on Discord where @kg and @radical discuss how to feed proper parameters in `benchmarks_ci.py` * Address PR feedback
1 parent 58ee5c7 commit 977c05e

File tree

2 files changed

+149
-0
lines changed

2 files changed

+149
-0
lines changed

src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Diagnostics.CodeAnalysis;
44
using System.IO;
55
using System.Linq;
6+
using System.Text;
67
using BenchmarkDotNet.Columns;
78
using BenchmarkDotNet.Configs;
89
using BenchmarkDotNet.Diagnosers;
@@ -75,6 +76,13 @@ public static (bool isSuccess, IConfig config, CommandLineOptions options) Parse
7576
{
7677
(bool isSuccess, IConfig config, CommandLineOptions options) result = default;
7778

79+
var (expandSuccess, expandedArgs) = ExpandResponseFile(args, logger);
80+
if (!expandSuccess)
81+
{
82+
return (false, default, default);
83+
}
84+
85+
args = expandedArgs;
7886
using (var parser = CreateParser(logger))
7987
{
8088
parser
@@ -86,6 +94,115 @@ public static (bool isSuccess, IConfig config, CommandLineOptions options) Parse
8694
return result;
8795
}
8896

97+
private static (bool Success, string[] ExpandedTokens) ExpandResponseFile(string[] args, ILogger logger)
98+
{
99+
List<string> result = new ();
100+
foreach (var arg in args)
101+
{
102+
if (arg.StartsWith("@"))
103+
{
104+
var fileName = arg.Substring(1);
105+
try
106+
{
107+
if (File.Exists(fileName))
108+
{
109+
var lines = File.ReadAllLines(fileName);
110+
foreach (var line in lines)
111+
{
112+
result.AddRange(ConsumeTokens(line));
113+
}
114+
}
115+
else
116+
{
117+
logger.WriteLineError($"Response file {fileName} does not exists.");
118+
return (false, Array.Empty<string>());
119+
}
120+
}
121+
catch (Exception ex)
122+
{
123+
logger.WriteLineError($"Failed to parse RSP file: {fileName}, {ex.Message}");
124+
return (false, Array.Empty<string>());
125+
}
126+
}
127+
else
128+
{
129+
if (arg.Contains(' '))
130+
{
131+
// Workaround for CommandLine library issue with parsing these kind of args.
132+
result.Add(" " + arg);
133+
}
134+
else
135+
{
136+
result.Add(arg);
137+
}
138+
}
139+
}
140+
141+
return (true, result.ToArray());
142+
}
143+
144+
private static IEnumerable<string> ConsumeTokens(string line)
145+
{
146+
bool insideQuotes = false;
147+
var token = new StringBuilder();
148+
for (int i = 0; i < line.Length; i++)
149+
{
150+
char currentChar = line[i];
151+
if (currentChar == ' ' && !insideQuotes)
152+
{
153+
if (token.Length > 0)
154+
{
155+
yield return GetToken();
156+
token = new StringBuilder();
157+
}
158+
159+
continue;
160+
}
161+
162+
if (currentChar == '"')
163+
{
164+
insideQuotes = !insideQuotes;
165+
continue;
166+
}
167+
168+
if (currentChar == '\\' && insideQuotes)
169+
{
170+
if (line[i + 1] == '"')
171+
{
172+
insideQuotes = false;
173+
i++;
174+
continue;
175+
}
176+
177+
if (line[i + 1] == '\\')
178+
{
179+
token.Append('\\');
180+
i++;
181+
continue;
182+
}
183+
}
184+
185+
token.Append(currentChar);
186+
}
187+
188+
if (token.Length > 0)
189+
{
190+
yield return GetToken();
191+
}
192+
193+
string GetToken()
194+
{
195+
var result = token.ToString();
196+
if (result.Contains(' '))
197+
{
198+
// Workaround for CommandLine library issue with parsing these kind of args.
199+
return " " + result;
200+
}
201+
202+
return result;
203+
}
204+
}
205+
89206
internal static bool TryUpdateArgs(string[] args, out string[]? updatedArgs, Action<CommandLineOptions> updater)
90207
{
91208
(bool isSuccess, CommandLineOptions options) result = default;

tests/BenchmarkDotNet.Tests/ConfigParserTests.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,38 @@ public void UsersCanSpecifyWithoutOverheadEvalution()
587587
}
588588
}
589589

590+
[Fact]
591+
public void UserCanSpecifyWasmArgs()
592+
{
593+
var parsedConfiguration = ConfigParser.Parse(new[] { "--runtimes", "wasm", "--wasmArgs", "--expose_wasm --module" }, new OutputLogger(Output));
594+
Assert.True(parsedConfiguration.isSuccess);
595+
var jobs = parsedConfiguration.config.GetJobs();
596+
foreach (var job in parsedConfiguration.config.GetJobs())
597+
{
598+
var wasmRuntime = Assert.IsType<WasmRuntime>(job.Environment.Runtime);
599+
Assert.Equal(" --expose_wasm --module", wasmRuntime.JavaScriptEngineArguments);
600+
}
601+
}
602+
603+
[Fact]
604+
public void UserCanSpecifyWasmArgsViaResponseFile()
605+
{
606+
var tempResponseFile = Path.GetRandomFileName();
607+
File.WriteAllLines(tempResponseFile, new[]
608+
{
609+
"--runtimes wasm",
610+
"--wasmArgs \"--expose_wasm --module\""
611+
});
612+
var parsedConfiguration = ConfigParser.Parse(new[] { $"@{tempResponseFile}" }, new OutputLogger(Output));
613+
Assert.True(parsedConfiguration.isSuccess);
614+
var jobs = parsedConfiguration.config.GetJobs();
615+
foreach (var job in parsedConfiguration.config.GetJobs())
616+
{
617+
var wasmRuntime = Assert.IsType<WasmRuntime>(job.Environment.Runtime);
618+
Assert.Equal(" --expose_wasm --module", wasmRuntime.JavaScriptEngineArguments);
619+
}
620+
}
621+
590622
[Theory]
591623
[InlineData("--filter abc", "--filter *")]
592624
[InlineData("-f abc", "--filter *")]

0 commit comments

Comments
 (0)