Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically generate properties for packet fields #21

Draft
wants to merge 55 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
4d579c6
Add source generator for Packet-type classes
TSRBerry Dec 2, 2024
8683792
Replace packet properties with PacketFieldAttribute where possible
TSRBerry Dec 2, 2024
9e101b1
Move builder classes to a new subfolder
TSRBerry Dec 2, 2024
448dbce
Split PacketGenerator into multiple files
TSRBerry Dec 2, 2024
f9c4915
Fix keyword order for Accessibility.ProtectedAndInternal
TSRBerry Dec 2, 2024
bf5863e
Restructure AccessorGenerator
TSRBerry Dec 2, 2024
695d301
Add indexer for Packet and remove PacketBytesFieldName constant
TSRBerry Dec 2, 2024
1fcce53
Use nameof() for type strings
TSRBerry Dec 3, 2024
6182ab1
PacketGenerator: Add support for strings
TSRBerry Dec 4, 2024
c01cf33
Apply PacketField to string properties
TSRBerry Dec 4, 2024
34362a5
PacketGenerator: Add support for simple array types
TSRBerry Dec 4, 2024
fd9ab59
Replace RegisterSourceOutput with RegisterImplementationSourceOutput
TSRBerry Dec 5, 2024
17d8304
PacketGenerator: Add AssumeGeneratedEnumType to field attribute
TSRBerry Dec 5, 2024
6a41547
Apply PacketField to generated and array properties
TSRBerry Dec 5, 2024
f047266
PacketGenerator: Remove TODO comment about grouping generated properties
TSRBerry Dec 5, 2024
d8fb2a1
PacketGenerator: Add ValidationMethod to packet field attribute
TSRBerry Dec 5, 2024
eba1bfa
Apply PacketField to properties with extra logic
TSRBerry Dec 5, 2024
863ebc6
PacketGenerator: Add special handling for IPAddress properties
TSRBerry Dec 6, 2024
a883698
Apply PacketField to IPAddress property
TSRBerry Dec 6, 2024
eed04da
Fix whitespace issues in post initialization sources
TSRBerry Dec 8, 2024
b886b7d
Remove global alias from fully qualified names
TSRBerry Dec 8, 2024
b0da16d
Fix code generation for property access modifiers
TSRBerry Dec 8, 2024
10a561a
Create test project for RyuSocks.Generator
TSRBerry Dec 8, 2024
f7dfd6b
PacketGenerator: Add tests for simple numeric property types
TSRBerry Dec 8, 2024
d1d1e0e
Add source generator testing package to Directory.Packages.props
TSRBerry Dec 8, 2024
c9366ad
PacketGenerator: Only assign to LengthMember if allowed
TSRBerry Dec 8, 2024
a3fcf8e
PacketGenerator: Generate correct accessor modifiers
TSRBerry Dec 8, 2024
99b50d6
PacketGenerator: Add simple string tests
TSRBerry Dec 8, 2024
5e7e5a3
Add missing copyright header
TSRBerry Dec 9, 2024
97b6d96
Remove unused import
TSRBerry Dec 9, 2024
939f169
Refactor Exception generation for better compatability
TSRBerry Dec 9, 2024
c4a1459
Add test for simple string with writable LengthMember
TSRBerry Dec 9, 2024
d704dcf
Remove PostInitGeneratedSources from SourceGeneratorVerifier
TSRBerry Dec 9, 2024
e60c496
Add tests for simple properties with OffsetMember
TSRBerry Dec 9, 2024
cf91af2
Replace BitConverter with BinaryPrimitives
TSRBerry Dec 9, 2024
d858906
PacketGenerator: Add tests for namespaces
TSRBerry Dec 10, 2024
b6e5f0d
Apply dotnet format changes
TSRBerry Dec 10, 2024
1b192ce
Invert if-statement to reduce nesting
TSRBerry Dec 11, 2024
0254e87
Return nullableWarnings directly
TSRBerry Dec 11, 2024
d622302
Move attribute parsing to a new class
TSRBerry Dec 11, 2024
c41f4dc
Extract common code from AccessorGenerator.Array methods
TSRBerry Dec 11, 2024
4d6bde7
Change return type to string for some AccessorGenerator.Simple methods
TSRBerry Dec 11, 2024
e860afe
Remove static default class in PacketFieldAttributeData
TSRBerry Dec 16, 2024
42cfac3
Add note to keep default values in sync
TSRBerry Jan 4, 2025
71c0a96
Add enum for string encodings to PacketFieldAttribute
TSRBerry Jan 4, 2025
83416b4
Generate encoding specific string accessors
TSRBerry Jan 4, 2025
ec6003a
Add tests for different string encodings
TSRBerry Jan 4, 2025
c4c450b
Remove enum string generation code
TSRBerry Jan 4, 2025
e514ce1
ExceptionHelper: Move common code to a new method
TSRBerry Jan 17, 2025
1c0ee69
Add ExcludeFromCodeCoverage attribute to relevant classes
TSRBerry Feb 15, 2025
1f5a91d
Make TypeConverterModel readonly
TSRBerry Feb 15, 2025
3018b9a
Parse both "variants" of actual type names
TSRBerry Feb 15, 2025
5c78b21
Add expected diagnostics to VerifyGeneratedSources
TSRBerry Feb 15, 2025
08eb5a7
Add exception test for bad attribute constructor args
TSRBerry Feb 15, 2025
7f7c0e0
Add test for generated enum types
TSRBerry Feb 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ concurrency:

jobs:
unit:
name: Unit tests (${{ matrix.config }})
name: Unit tests (${{ matrix.project }} - ${{ matrix.config }})
runs-on: ubuntu-latest
strategy:
matrix:
project: [RyuSocks.Test, RyuSocks.Generator.Test]
config: [Debug, Release]
steps:
- uses: actions/checkout@v4
Expand All @@ -29,7 +30,7 @@ jobs:
uses: TSRBerry/unstable-commands@v1
with:
commands: dotnet test -c "${{ matrix.config }}"
working-directory: RyuSocks.Test
working-directory: ${{ matrix.project }}
timeout-minutes: 10
retry-codes: 139

Expand Down
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<PackageVersion Include="FluentFTP" Version="50.1.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing" Version="1.1.2" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageVersion Include="Moq" Version="4.20.70" />
<PackageVersion Include="NetCoreServer" Version="8.0.7" />
Expand Down
65 changes: 65 additions & 0 deletions RyuSocks.Generator.Test/PacketGeneratorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System.IO;
using System.Threading.Tasks;
using Xunit;
using Verify = RyuSocks.Generator.Test.SourceGeneratorVerifier<RyuSocks.Generator.PacketGenerator>;

namespace RyuSocks.Generator.Test
{
public class PacketGeneratorTests
{
private const string DataDirectory = "data/PacketGenerator/";

private static string GetSourceFromFile(string path) => File.ReadAllText(DataDirectory + path);
private static (string filename, string content) GetGeneratedSourceFromFile(string path) =>
(Path.GetFileName(path), File.ReadAllText(DataDirectory + path));

[Theory]
[InlineData("SimpleProps", "TestPacket.cs",
"TestPacket.Byte1.g.cs", "TestPacket.SByte1.g.cs",
"TestPacket.UShort1.g.cs", "TestPacket.Short1.g.cs",
"TestPacket.UInt1.g.cs", "TestPacket.Int1.g.cs",
"TestPacket.ULong1.g.cs", "TestPacket.Long1.g.cs",
"TestPacket.String1.g.cs", "TestPacket.String2.g.cs", "TestPacket.String3.g.cs")]
[InlineData("SimplePropsOffsetMember", "TestPacket.cs",
"TestPacket.Byte1.g.cs", "TestPacket.SByte1.g.cs",
"TestPacket.UShort1.g.cs", "TestPacket.Short1.g.cs",
"TestPacket.UInt1.g.cs", "TestPacket.Int1.g.cs",
"TestPacket.ULong1.g.cs", "TestPacket.Long1.g.cs",
"TestPacket.String1.g.cs", "TestPacket.String2.g.cs", "TestPacket.String3.g.cs")]
[InlineData("Namespaces", "TestPacket.cs",
"TestSpace.TestPacket.TestField.g.cs", "TestSpace.TestPacket1.FirstField.g.cs",
"AnotherSpace.TestPacket.TestField.g.cs", "AnotherSpace.TestPacket2.SecondField.g.cs")]
[InlineData("StringEncodings", "TestPacket.cs",
"LittleEndian.ASCIIPacket.String1.g.cs", "LittleEndian.ASCIIPacket.String2.g.cs", "LittleEndian.ASCIIPacket.String3.g.cs", "LittleEndian.ASCIIPacket.String4.g.cs",
"LittleEndian.UnicodePacket.String1.g.cs", "LittleEndian.UnicodePacket.String2.g.cs", "LittleEndian.UnicodePacket.String3.g.cs", "LittleEndian.UnicodePacket.String4.g.cs",
"LittleEndian.UTF7Packet.String1.g.cs", "LittleEndian.UTF7Packet.String2.g.cs", "LittleEndian.UTF7Packet.String3.g.cs", "LittleEndian.UTF7Packet.String4.g.cs",
"LittleEndian.UTF8Packet.String1.g.cs", "LittleEndian.UTF8Packet.String2.g.cs", "LittleEndian.UTF8Packet.String3.g.cs", "LittleEndian.UTF8Packet.String4.g.cs",
"LittleEndian.UTF32Packet.String1.g.cs", "LittleEndian.UTF32Packet.String2.g.cs", "LittleEndian.UTF32Packet.String3.g.cs", "LittleEndian.UTF32Packet.String4.g.cs",
"BigEndian.ASCIIPacket.String1.g.cs", "BigEndian.ASCIIPacket.String2.g.cs", "BigEndian.ASCIIPacket.String3.g.cs", "BigEndian.ASCIIPacket.String4.g.cs",
"BigEndian.UnicodePacket.String1.g.cs", "BigEndian.UnicodePacket.String2.g.cs", "BigEndian.UnicodePacket.String3.g.cs", "BigEndian.UnicodePacket.String4.g.cs",
"BigEndian.UTF7Packet.String1.g.cs", "BigEndian.UTF7Packet.String2.g.cs", "BigEndian.UTF7Packet.String3.g.cs", "BigEndian.UTF7Packet.String4.g.cs",
"BigEndian.UTF8Packet.String1.g.cs", "BigEndian.UTF8Packet.String2.g.cs", "BigEndian.UTF8Packet.String3.g.cs", "BigEndian.UTF8Packet.String4.g.cs",
"BigEndian.UTF32Packet.String1.g.cs", "BigEndian.UTF32Packet.String2.g.cs", "BigEndian.UTF32Packet.String3.g.cs", "BigEndian.UTF32Packet.String4.g.cs")]
public async Task GeneratedSources_AsExpected(string directory, string sourcePath, params string[] expectedGeneratedSourcePath)
{
const int DefaultSourcesAmount = 3;
(string, string)[] generatedSources = new (string, string)[expectedGeneratedSourcePath.Length + DefaultSourcesAmount];

generatedSources[0] = GetGeneratedSourceFromFile("StringEncoding.g.cs");
generatedSources[1] = GetGeneratedSourceFromFile("PacketFieldAttribute.g.cs");
generatedSources[2] = GetGeneratedSourceFromFile("Packet.g.cs");

directory += "/";

for (int i = 0; i < expectedGeneratedSourcePath.Length; i++)
{
generatedSources[i + DefaultSourcesAmount] = GetGeneratedSourceFromFile(directory + expectedGeneratedSourcePath[i]);
}

await Verify.VerifyGeneratedSources(
GetSourceFromFile(directory + sourcePath),
generatedSources
);
}
}
}
29 changes: 29 additions & 0 deletions RyuSocks.Generator.Test/RyuSocks.Generator.Test.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing" PrivateAssets="all" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\RyuSocks.Generator\RyuSocks.Generator.csproj" />
</ItemGroup>

<ItemGroup>
<Compile Remove="data\**" />
<None Include="data\**" CopyToOutputDirectory="Always"/>
</ItemGroup>
</Project>
47 changes: 47 additions & 0 deletions RyuSocks.Generator.Test/SourceGeneratorVerifier.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;

namespace RyuSocks.Generator.Test
{
public static class SourceGeneratorVerifier<TSourceGenerator>
where TSourceGenerator : new()
{
public static Task VerifyGeneratedSources(string source, params (string filename, string content)[] generatedSources)
{
var test = new Test
{
TestCode = source
};

foreach ((string filename, string content) in generatedSources)
{
test.TestState.GeneratedSources.Add((typeof(TSourceGenerator), filename, content));
}

return test.RunAsync();
}

public class Test : CSharpSourceGeneratorTest<TSourceGenerator, DefaultVerifier>
{
protected override CompilationOptions CreateCompilationOptions()
{
var compilationOptions = base.CreateCompilationOptions();
return compilationOptions.WithSpecificDiagnosticOptions(
compilationOptions.SpecificDiagnosticOptions.SetItems(GetNullableWarningsFromCompiler()));
}

private static ImmutableDictionary<string, ReportDiagnostic> GetNullableWarningsFromCompiler()
{
string[] args = ["/warnaserror:nullable"];
var commandLineArguments = CSharpCommandLineParser.Default.Parse(args, baseDirectory: Environment.CurrentDirectory, sdkDirectory: Environment.CurrentDirectory);
return commandLineArguments.CompilationOptions.SpecificDiagnosticOptions;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using RyuSocks.Packets;
using System;
using System.Buffers.Binary;
using System.Text;

namespace AnotherSpace
{
partial class TestPacket
{
public partial byte TestField
{
get
{
return this[4];
}
set
{
this[4] = value;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using RyuSocks.Packets;
using System;
using System.Buffers.Binary;
using System.Text;

namespace AnotherSpace
{
partial class TestPacket2
{
private partial byte SecondField
{
get
{
return this[2];
}
set
{
this[2] = value;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using RyuSocks.Packets;

namespace TestSpace
{
public partial class TestPacket : Packet
{
[PacketField(0)]
internal partial byte TestField { get; set; }
}

partial class TestPacket1 : Packet
{
[PacketField(1)]
public partial byte FirstField { get; set; }
}
}

namespace AnotherSpace
{
partial class TestPacket : Packet
{
[PacketField(4)]
public partial byte TestField { get; set; }
}

internal partial class TestPacket2 : Packet
{
[PacketField(2)]
private partial byte SecondField { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using RyuSocks.Packets;
using System;
using System.Buffers.Binary;
using System.Text;

namespace TestSpace
{
partial class TestPacket
{
internal partial byte TestField
{
get
{
return this[0];
}
set
{
this[0] = value;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using RyuSocks.Packets;
using System;
using System.Buffers.Binary;
using System.Text;

namespace TestSpace
{
partial class TestPacket1
{
public partial byte FirstField
{
get
{
return this[1];
}
set
{
this[1] = value;
}
}
}
}
42 changes: 42 additions & 0 deletions RyuSocks.Generator.Test/data/PacketGenerator/Packet.g.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;

namespace RyuSocks.Packets
{
public abstract partial class Packet
{
/// <summary>
/// The contents of the packet.
/// </summary>
public byte[] Bytes { get; protected set; }

/// <inheritdoc cref="Bytes"/>
public byte this[int i]
{
get => Bytes[i];
set => Bytes[i] = value;
}

/// <summary>
/// Creates a new span over the packet.
/// </summary>
/// <seealso cref="Bytes"/>
/// <seealso cref="M:System.MemoryExtensions.AsSpan``1(``0[])"/>
public Span<byte> AsSpan() => Bytes;

/// <summary>
/// Creates a new Span over the portion of the packet beginning
/// at 'start' index and ending at 'end' index (exclusive).
/// </summary>
/// <param name="start">The index at which to begin the Span.</param>
/// <param name="length">The number of items in the Span.</param>
/// <seealso cref="M:System.MemoryExtensions.AsSpan``1(``0[],System.Int32,System.Int32)"/>
public Span<byte> AsSpan(int start, int length) => Bytes.AsSpan(start, length);

protected Packet() { }

protected Packet(byte[] bytes)
{
Bytes = bytes;
}
}
}
Loading
Loading