Skip to content

Commit ecb25bf

Browse files
Pass non escaped strings to generated project (#2136)
1 parent 95bb2aa commit ecb25bf

File tree

5 files changed

+99
-13
lines changed

5 files changed

+99
-13
lines changed

src/BenchmarkDotNet/Exporters/FullNameProvider.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,9 @@ private static string GetArgument(object argumentValue, Type argumentType)
141141
case object[] array when array.Length == 1:
142142
return GetArgument(array[0], argumentType);
143143
case string text:
144-
return $"\"{EscapeWhitespaces(text)}\"";
144+
return text.EscapeSpecialCharacters(true);
145145
case char character:
146-
return $"'{character}'";
146+
return character.EscapeSpecialCharacter(true);
147147
case DateTime time:
148148
return time.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffffffK");
149149
case Type type:
@@ -183,10 +183,6 @@ private static string GetArray(IEnumerable collection)
183183
return buffer.ToString();
184184
}
185185

186-
private static string EscapeWhitespaces(string text)
187-
=> text.Replace("\t", "\\t")
188-
.Replace("\r\n", "\\r\\n");
189-
190186
private static string GetTypeArgumentName(Type type)
191187
{
192188
if (Aliases.TryGetValue(type, out string alias))

src/BenchmarkDotNet/Extensions/StringAndTextExtensions.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,16 @@ internal static string EscapeSpecialCharacters(this string str, bool quote)
3737
return Microsoft.CodeAnalysis.CSharp.SymbolDisplay.FormatLiteral(str, quote);
3838
}
3939

40+
/// <summary>
41+
/// Escapes UNICODE control character
42+
/// </summary>
43+
/// <param name="c">char to escape</param>
44+
/// <param name="quote">True to put (single) quotes around the character literal.</param>
45+
internal static string EscapeSpecialCharacter(this char c, bool quote)
46+
{
47+
return Microsoft.CodeAnalysis.CSharp.SymbolDisplay.FormatLiteral(c, quote);
48+
}
49+
4050
/// <summary>
4151
/// replaces all invalid file name chars with their number representation
4252
/// </summary>

src/BenchmarkDotNet/Helpers/SourceCodeHelper.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ public static string ToSourceCode(object value)
1818
case bool b:
1919
return b.ToLowerCase();
2020
case string text:
21-
return $"$@\"{text.Replace("\"", "\"\"").Replace("{", "{{").Replace("}", "}}")}\"";
21+
return text.EscapeSpecialCharacters(true);
2222
case char c:
23-
return c == '\\' ? "'\\\\'" : $"'{value}'";
23+
return c.EscapeSpecialCharacter(true);
2424
case float f:
2525
return ToSourceCode(f);
2626
case double d:

tests/BenchmarkDotNet.IntegrationTests/ParamsTests.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,57 @@ public class InvalidFileNamesInParams
139139
public void Benchmark() => Console.WriteLine("// " + Field);
140140
}
141141

142+
[Fact]
143+
public void SpecialCharactersInStringAreSupported() => CanExecute<CompileSpecialCharactersInString>();
144+
145+
public class CompileSpecialCharactersInString
146+
{
147+
[Params("\0")] public string Null;
148+
[Params("\t")] public string Tab;
149+
[Params("\n")] public string NewLine;
150+
[Params("\\")] public string Slash;
151+
[Params("\"")] public string Quote;
152+
[Params("\u0061")] public string Unicode;
153+
[Params("{")] public string Bracket;
154+
155+
[Params("\n \0 \n")] public string Combo;
156+
157+
[Params("C:\\file1.txt")] public string Path1;
158+
[Params(@"C:\file2.txt")] public string Path2;
159+
160+
[Benchmark]
161+
public void Benchmark()
162+
{
163+
var isPassedAsSingleCharacter =
164+
Null.Length == 1 &&
165+
Tab.Length == 1 &&
166+
NewLine.Length == 1 &&
167+
Slash.Length == 1 &&
168+
Quote.Length == 1 &&
169+
Unicode.Length == 1 &&
170+
Bracket.Length == 1;
171+
172+
if (!isPassedAsSingleCharacter)
173+
throw new InvalidOperationException("Some Param has an invalid escaped string");
174+
}
175+
}
176+
177+
[Fact]
178+
public void SpecialCharactersInCharAreSupported() => CanExecute<CompileSpecialCharactersInChar>();
179+
180+
public class CompileSpecialCharactersInChar
181+
{
182+
[Params('\0')] public char Null;
183+
[Params('\t')] public char Tab;
184+
[Params('\n')] public char NewLine;
185+
[Params('\\')] public char Slash;
186+
[Params('\"')] public char Quote;
187+
[Params('\u0061')] public char Unicode;
188+
189+
[Benchmark]
190+
public void Benchmark() { }
191+
}
192+
142193
[Fact]
143194
public void ParamsMustBeEscapedProperly() => CanExecute<NeedEscaping>();
144195

tests/BenchmarkDotNet.Tests/SourceCodeHelperTests.cs

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,16 @@ namespace BenchmarkDotNet.Tests
99
// TODO: add decimal, typeof, CreateInstance, TimeValue, IntPtr, IFormattable
1010
public class SourceCodeHelperTests
1111
{
12-
private ITestOutputHelper output;
12+
private readonly ITestOutputHelper output;
1313

1414
public SourceCodeHelperTests(ITestOutputHelper output) => this.output = output;
1515

1616
[Theory]
1717
[InlineData(null, "null")]
1818
[InlineData(false, "false")]
1919
[InlineData(true, "true")]
20-
[InlineData("string", "$@\"string\"")]
21-
[InlineData("string/\\", @"$@""string/\""")]
20+
[InlineData("string", "\"string\"")]
21+
[InlineData("string/\\", @"""string/\\""")]
2222
[InlineData('a', "'a'")]
2323
[InlineData('\\', "'\\\\'")]
2424
[InlineData(0.123f, "0.123f")]
@@ -43,7 +43,7 @@ public void SupportsGuid()
4343
[Fact]
4444
public void CanEscapeJson()
4545
{
46-
const string expected = "$@\"{{ \"\"message\"\": \"\"Hello, World!\"\" }}\"";
46+
const string expected = "\"{ \\\"message\\\": \\\"Hello, World!\\\" }\"";
4747

4848
var actual = SourceCodeHelper.ToSourceCode("{ \"message\": \"Hello, World!\" }");
4949

@@ -53,11 +53,40 @@ public void CanEscapeJson()
5353
[Fact]
5454
public void CanEscapePath()
5555
{
56-
const string expected = @"$@""C:\Projects\BenchmarkDotNet\samples\BenchmarkDotNet.Samples""";
56+
const string expected = @"""C:\\Projects\\BenchmarkDotNet\\samples\\BenchmarkDotNet.Samples""";
5757

5858
var actual = SourceCodeHelper.ToSourceCode(@"C:\Projects\BenchmarkDotNet\samples\BenchmarkDotNet.Samples");
5959

6060
Assert.Equal(expected, actual);
6161
}
62+
63+
[Fact]
64+
public void CanEscapeControlCharacters()
65+
{
66+
const string expected = @""" \0 \b \f \n \t \v \"" a a a a { } """;
67+
68+
var actual = SourceCodeHelper.ToSourceCode(" \0 \b \f \n \t \v \" \u0061 \x0061 \x61 \U00000061 { } ");
69+
70+
Assert.Equal(expected, actual);
71+
}
72+
73+
[Theory]
74+
[InlineData('\0', @"'\0'")]
75+
[InlineData('\b', @"'\b'")]
76+
[InlineData('\f', @"'\f'")]
77+
[InlineData('\n', @"'\n'")]
78+
[InlineData('\t', @"'\t'")]
79+
[InlineData('\v', @"'\v'")]
80+
[InlineData('\'', @"'\''")]
81+
[InlineData('\u0061', "'a'")]
82+
[InlineData('"', "'\"'")]
83+
[InlineData('{', "'{'")]
84+
[InlineData('}', "'}'")]
85+
public void CanEscapeControlCharactersInChar(char original, string excepted)
86+
{
87+
var actual = SourceCodeHelper.ToSourceCode(original);
88+
89+
Assert.Equal(excepted, actual);
90+
}
6291
}
6392
}

0 commit comments

Comments
 (0)