Skip to content

Commit

Permalink
Backport some improvements from branch 'kick-support' (#915)
Browse files Browse the repository at this point in the history
* Create ReadOnlySpanTryReplaceNonEscapedTests.DoesNotSupportReplacingEscapeChars

* Only target net6.0 for TwitchDownloaderCore.Tests

* Improve performance of TimeSpanHFormat and return TimeSpan.ToString when format is null or empty

* Update ReadOnlySpanExtensions

* Use non-memory overload to prevent internal reallocation

* Consolidate ChatCompression, ChatFormat, and TimestampFormat into Enums.cs

* Relax IO access restrictions

* Backport M3U8 parser and ReadOnlySpanExtensions: Count, UnEscapedIndexOf, and UnEscapedIndexOfAny & related tests

* Implement M3U8 parser
  • Loading branch information
ScrubN authored Dec 15, 2023
1 parent 1c5a402 commit e384cd6
Show file tree
Hide file tree
Showing 25 changed files with 1,644 additions and 234 deletions.
2 changes: 1 addition & 1 deletion TwitchDownloaderCLI/Modes/Arguments/ChatDownloadArgs.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using CommandLine;
using TwitchDownloaderCore.Chat;
using TwitchDownloaderCore.Tools;

namespace TwitchDownloaderCLI.Modes.Arguments
{
Expand Down
2 changes: 1 addition & 1 deletion TwitchDownloaderCLI/Modes/Arguments/ChatUpdateArgs.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using CommandLine;
using TwitchDownloaderCore.Chat;
using TwitchDownloaderCore.Tools;

namespace TwitchDownloaderCLI.Modes.Arguments
{
Expand Down
1 change: 0 additions & 1 deletion TwitchDownloaderCLI/Modes/DownloadChat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using TwitchDownloaderCLI.Modes.Arguments;
using TwitchDownloaderCLI.Tools;
using TwitchDownloaderCore;
using TwitchDownloaderCore.Chat;
using TwitchDownloaderCore.Options;
using TwitchDownloaderCore.Tools;

Expand Down
2 changes: 1 addition & 1 deletion TwitchDownloaderCLI/Modes/UpdateChat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
using TwitchDownloaderCLI.Modes.Arguments;
using TwitchDownloaderCLI.Tools;
using TwitchDownloaderCore;
using TwitchDownloaderCore.Chat;
using TwitchDownloaderCore.Options;
using TwitchDownloaderCore.Tools;

namespace TwitchDownloaderCLI.Modes
{
Expand Down
423 changes: 423 additions & 0 deletions TwitchDownloaderCore.Tests/M3U8Tests.cs

Large diffs are not rendered by default.

44 changes: 44 additions & 0 deletions TwitchDownloaderCore.Tests/ReadOnlySpanCountTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using TwitchDownloaderCore.Extensions;

namespace TwitchDownloaderCore.Tests
{
public class ReadOnlySpanCountTests
{
[Fact]
public void ReturnsNegativeOneWhenNotPresent()
{
ReadOnlySpan<char> str = "SORRY FOR THE TRAFFIC NaM";
const int EXPECTED = -1;

var actual = str.Count('L');

Assert.Equal(EXPECTED, actual);
}

[Fact]
public void ReturnsNegativeOneForEmptyString()
{
ReadOnlySpan<char> str = "";
const int EXPECTED = -1;

var actual = str.Count('L');

Assert.Equal(EXPECTED, actual);
}

[Theory]
[InlineData('S', 1)]
[InlineData('R', 4)]
[InlineData('a', 1)]
[InlineData('F', 3)]
[InlineData('M', 1)]
public void ReturnsCorrectCharacterCount(char character, int expectedCount)
{
ReadOnlySpan<char> str = "SORRY FOR THE TRAFFIC NaM";

var actual = str.Count(character);

Assert.Equal(expectedCount, actual);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,16 @@ public void DoesNotEscapeDifferingQuotes()

Assert.False(success);
}

[Fact]
public void DoesNotSupportReplacingEscapeChars()
{
ReadOnlySpan<char> str = "\"SORRY FOR\" TRAFFIC NaM.\"";
var destination = new char[str.Length];

var success = str.TryReplaceNonEscaped(destination, '\"', 'W');

Assert.False(success);
}
}
}
115 changes: 115 additions & 0 deletions TwitchDownloaderCore.Tests/ReadOnlySpanUnEscapedIndexOfAnyTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
using TwitchDownloaderCore.Extensions;

namespace TwitchDownloaderCore.Tests
{
public class ReadOnlySpanUnEscapedIndexOfAnyTests
{
[Fact]
public void CorrectlyFindsNextIndexWithoutEscapes()
{
ReadOnlySpan<char> str = "SORRY FOR TRAFFIC NaM";
const string CHARS_TO_FIND = "abc";
const int CHAR_INDEX = 19;

var actual = str.UnEscapedIndexOfAny(CHARS_TO_FIND);

Assert.Equal(CHAR_INDEX, actual);
}

[Fact]
public void DoesNotFindAnIndexWhenNotPresent()
{
ReadOnlySpan<char> str = "SORRY FOR TRAFFIC NaM";
const string CHARS_TO_FIND = "LP";
const int CHAR_INDEX = -1;

var actual = str.UnEscapedIndexOfAny(CHARS_TO_FIND);

Assert.Equal(CHAR_INDEX, actual);
}

[Fact]
public void CorrectlyFindsNextIndexWithBackslashEscapes()
{
ReadOnlySpan<char> str = @"SORRY \FOR TRAFFIC NaM";
const string CHARS_TO_FIND = "FT";
const int CHAR_INDEX = 11;

var actual = str.UnEscapedIndexOfAny(CHARS_TO_FIND);

Assert.Equal(CHAR_INDEX, actual);
}

[Fact]
public void DoesNotFindIndexWithBackslashEscapes()
{
ReadOnlySpan<char> str = @"SORRY \FOR TRA\F\F\IC NaM";
const string CHARS_TO_FIND = "FI";
const int CHAR_INDEX = -1;

var actual = str.UnEscapedIndexOfAny(CHARS_TO_FIND);

Assert.Equal(CHAR_INDEX, actual);
}

[Fact]
public void CorrectlyFindsNextIndexWithUnrelatedQuoteEscapes()
{
ReadOnlySpan<char> str = "SORRY FOR \"TRAFFIC\" NaM";
const string CHARS_TO_FIND = "abc";
const int CHAR_INDEX = 21;

var actual = str.UnEscapedIndexOfAny(CHARS_TO_FIND);

Assert.Equal(CHAR_INDEX, actual);
}

[Fact]
public void CorrectlyFindsNextIndexWithQuoteEscapes()
{
ReadOnlySpan<char> str = "SORRY \"FOR\" TRAFFIC NaM";
const string CHARS_TO_FIND = "FM";
const int CHAR_INDEX = 15;

var actual = str.UnEscapedIndexOfAny(CHARS_TO_FIND);

Assert.Equal(CHAR_INDEX, actual);
}

[Fact]
public void DoesNotFindAnIndexWithQuoteEscapes()
{
ReadOnlySpan<char> str = "SORRY \"FOR\" \"TRAFFIC\" NaM";
const string CHARS_TO_FIND = "FA";
const int CHAR_INDEX = -1;

var actual = str.UnEscapedIndexOfAny(CHARS_TO_FIND);

Assert.Equal(CHAR_INDEX, actual);
}

[Theory]
[InlineData("abc\\")]
[InlineData("abc\'")]
[InlineData("abc\"")]
public void Throws_WhenEscapeCharIsPassed(string charsToFind)
{
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
ReadOnlySpan<char> str = "SO\\R\\RY \'FOR\' \"TRAFFIC\" NaM";
str.UnEscapedIndexOfAny(charsToFind);
});
}

[Fact]
public void Throws_WhenImbalancedQuoteChar()
{
Assert.Throws<FormatException>(() =>
{
const string CHARS_TO_FIND = "FT";
ReadOnlySpan<char> str = "SORRY \"FOR TRAFFIC NaM";
str.UnEscapedIndexOfAny(CHARS_TO_FIND);
});
}
}
}
115 changes: 115 additions & 0 deletions TwitchDownloaderCore.Tests/ReadOnlySpanUnEscapedIndexOfTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
using TwitchDownloaderCore.Extensions;

namespace TwitchDownloaderCore.Tests
{
public class ReadOnlySpanUnEscapedIndexOfTests
{
[Fact]
public void CorrectlyFindsNextIndexWithoutEscapes()
{
ReadOnlySpan<char> str = "SORRY FOR TRAFFIC NaM";
const char CHAR_TO_FIND = 'a';
const int CHAR_INDEX = 19;

var actual = str.UnEscapedIndexOf(CHAR_TO_FIND);

Assert.Equal(CHAR_INDEX, actual);
}

[Fact]
public void DoesNotFindAnIndexWhenNotPresent()
{
ReadOnlySpan<char> str = "SORRY FOR TRAFFIC NaM";
const char CHAR_TO_FIND = 'L';
const int CHAR_INDEX = -1;

var actual = str.UnEscapedIndexOf(CHAR_TO_FIND);

Assert.Equal(CHAR_INDEX, actual);
}

[Fact]
public void CorrectlyFindsNextIndexWithBackslashEscapes()
{
ReadOnlySpan<char> str = @"SORRY \FOR TRAFFIC NaM";
const char CHAR_TO_FIND = 'F';
const int CHAR_INDEX = 14;

var actual = str.UnEscapedIndexOf(CHAR_TO_FIND);

Assert.Equal(CHAR_INDEX, actual);
}

[Fact]
public void DoesNotFindIndexWithBackslashEscapes()
{
ReadOnlySpan<char> str = @"SORRY \FOR TRA\F\FIC NaM";
const char CHAR_TO_FIND = 'F';
const int CHAR_INDEX = -1;

var actual = str.UnEscapedIndexOf(CHAR_TO_FIND);

Assert.Equal(CHAR_INDEX, actual);
}

[Fact]
public void CorrectlyFindsNextIndexWithUnrelatedQuoteEscapes()
{
ReadOnlySpan<char> str = "SORRY FOR \"TRAFFIC\" NaM";
const char CHAR_TO_FIND = 'a';
const int CHAR_INDEX = 21;

var actual = str.UnEscapedIndexOf(CHAR_TO_FIND);

Assert.Equal(CHAR_INDEX, actual);
}

[Fact]
public void CorrectlyFindsNextIndexWithQuoteEscapes()
{
ReadOnlySpan<char> str = "SORRY \"FOR\" TRAFFIC NaM";
const char CHAR_TO_FIND = 'F';
const int CHAR_INDEX = 15;

var actual = str.UnEscapedIndexOf(CHAR_TO_FIND);

Assert.Equal(CHAR_INDEX, actual);
}

[Fact]
public void DoesNotFindAnIndexWithQuoteEscapes()
{
ReadOnlySpan<char> str = "SORRY \"FOR\" \"TRAFFIC\" NaM";
const char CHAR_TO_FIND = 'F';
const int CHAR_INDEX = -1;

var actual = str.UnEscapedIndexOf(CHAR_TO_FIND);

Assert.Equal(CHAR_INDEX, actual);
}

[Theory]
[InlineData('\\')]
[InlineData('\'')]
[InlineData('\"')]
public void Throws_WhenEscapeCharIsPassed(char charToFind)
{
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
ReadOnlySpan<char> str = "SO\\R\\RY \'FOR\' \"TRAFFIC\" NaM";
str.UnEscapedIndexOf(charToFind);
});
}

[Fact]
public void Throws_WhenImbalancedQuoteChar()
{
Assert.Throws<FormatException>(() =>
{
const char CHAR_TO_FIND = 'F';
ReadOnlySpan<char> str = "SORRY \"FOR TRAFFIC NaM";
str.UnEscapedIndexOf(CHAR_TO_FIND);
});
}
}
}
20 changes: 16 additions & 4 deletions TwitchDownloaderCore.Tests/TimeSpanHFormatTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public void CustomFormatOverloadMatchesICustomFormatter()
var timeSpan = new TimeSpan(17, 49, 12);
const string FORMAT_STRING = @"HH\:mm\:ss";

var resultICustomFormatter = ((ICustomFormatter)TimeSpanHFormat.ReusableInstance).Format(FORMAT_STRING, timeSpan,null);
var resultICustomFormatter = ((ICustomFormatter)TimeSpanHFormat.ReusableInstance).Format(FORMAT_STRING, timeSpan, null);
var resultCustom = TimeSpanHFormat.ReusableInstance.Format(FORMAT_STRING, timeSpan);

Assert.Equal(resultICustomFormatter, resultCustom);
Expand Down Expand Up @@ -77,15 +77,27 @@ public void CorrectlyFormatsNull()
}

[Fact]
public void ReturnsEmptyString_WhenFormatIsEmpty()
public void ReturnsTimeSpanToString_WhenFormatIsEmpty()
{
var timeSpan = new TimeSpan(17, 49, 12);
const string FORMAT_STRING = "";
const string EXPECTED = "";
var expected = timeSpan.ToString();

var resultCustom = TimeSpanHFormat.ReusableInstance.Format(FORMAT_STRING, timeSpan);

Assert.Equal(EXPECTED, resultCustom);
Assert.Equal(expected, resultCustom);
}

[Fact]
public void ReturnsTimeSpanToString_WhenFormatIsNull()
{
var timeSpan = new TimeSpan(17, 49, 12);
const string FORMAT_STRING = null!;
var expected = timeSpan.ToString();

var resultCustom = TimeSpanHFormat.ReusableInstance.Format(FORMAT_STRING, timeSpan);

Assert.Equal(expected, resultCustom);
}

[Fact]
Expand Down
4 changes: 2 additions & 2 deletions TwitchDownloaderCore.Tests/TwitchDownloaderCore.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net6.0;net6.0-windows</TargetFrameworks>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<LangVersion>latest</LangVersion>
<LangVersion>default</LangVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
9 changes: 0 additions & 9 deletions TwitchDownloaderCore/Chat/ChatCompression.cs

This file was deleted.

Loading

0 comments on commit e384cd6

Please sign in to comment.