Skip to content

Commit

Permalink
feat: gRPC transcoding improvements
Browse files Browse the repository at this point in the history
This includes:

- Implementing pattern matching
- Implementing additional binding matching
- Implementing the data-driven tests

(I've changed HttpRulePathPattern to use a file-scoped namespace as so much of it was changing anyway that the diffs would already be a mess.)
  • Loading branch information
jskeet committed Jul 21, 2022
1 parent 64dc4d3 commit 0d20c00
Show file tree
Hide file tree
Showing 8 changed files with 497 additions and 192 deletions.
37 changes: 37 additions & 0 deletions Google.Api.Gax.Grpc.Tests/ConformanceTestData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2022 Google LLC
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file or at
* https://developers.google.com/open-source/licenses/bsd
*/

using System;
using System.IO;

namespace Google.Api.Gax.Grpc.Tests;

/// <summary>
/// Utility code for locating and loading test data.
/// </summary>
public static class ConformanceTestData
{
/// <summary>
/// Finds the root directory of the repository by looking for common files.
/// </summary>
public static string FindRepositoryRootDirectory()
{
var currentDirectory = Path.GetFullPath(".");
var directory = new DirectoryInfo(currentDirectory);
while (directory != null &&
(!File.Exists(Path.Combine(directory.FullName, "LICENSE"))
|| !Directory.Exists(Path.Combine(directory.FullName, "Google.Api.CommonProtos"))))
{
directory = directory.Parent;
}
if (directory == null)
{
throw new InvalidOperationException("Unable to determine root directory. Please run within gax-dotnet repository.");
}
return directory.FullName;
}
}
33 changes: 23 additions & 10 deletions Google.Api.Gax.Grpc.Tests/Rest/HttpRulePathPatternTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ namespace Google.Api.Gax.Grpc.Rest.Tests
{
public class HttpRulePathPatternTest
{
public static TheoryData<string, RuleTestRequest, string> ValidPatternData = new TheoryData<string, RuleTestRequest, string>
// We want to end up with theory parameters that are all serializable for XUnit, but avoid calling ToString in each line of the test description.
public static TheoryData<string, string, string> ValidPatternData = ConvertTheoryData(new TheoryData<string, RuleTestRequest, string>
{
{ "x/y:custom", new RuleTestRequest(), "x/y:custom" },
{ "firstPart/{x}/secondPart/{y}", new RuleTestRequest { X = "x1", Y = "y2" }, "firstPart/x1/secondPart/y2" },
Expand All @@ -24,31 +25,43 @@ public class HttpRulePathPatternTest
{ "pattern/{x=abc/**}", new RuleTestRequest { X = "abc/def/ghi" }, "pattern/abc/def/ghi" },
{ "pattern/{x=**}", new RuleTestRequest { X = "abc/New York" }, "pattern/abc/New%20York" },
{ "nested/{nested.a}", new RuleTestRequest { Nested = new RuleTestRequest.Types.Nested { A = "aaa" } }, "nested/aaa" },
// The segment resolves to an empty string instead of failing
{ "nested/{nested.a}/end", new RuleTestRequest(), "nested//end" }
};
// The nested field isn't present, so this doesn't match.
{ "nested/{nested.a}/end", new RuleTestRequest(), null },
{ "before/{int}/end", new RuleTestRequest { Int = 5 }, "before/5/end" },
});

private static TheoryData<string, string, string> ConvertTheoryData(TheoryData<string, RuleTestRequest, string> theoryData)
{
var ret = new TheoryData<string, string, string>();
foreach (var item in theoryData)
{
ret.Add((string) item[0], ((RuleTestRequest) item[1]).ToString(), (string) item[2]);
}
return ret;
}

[Theory]
[MemberData(nameof(ValidPatternData))]
public void ValidPattern(string pattern, RuleTestRequest request, string expectedFormatResult)
public void ValidPattern(string pattern, string requestJson, string expectedFormatResult)
{
var rulePathPattern = ParsePattern(pattern);
string actualFormatResult = rulePathPattern.Format(request);
var request = RuleTestRequest.Parser.ParseJson(requestJson);
string actualFormatResult = rulePathPattern.TryFormat(request);
Assert.Equal(expectedFormatResult, actualFormatResult);
}

[Theory]
[InlineData("before/{unterminated-brace/end", Skip = "We don't detect this at the moment.")]
[InlineData("before/{unterminated-brace/end")]
[InlineData("before/unstarted-brace}/end")]
[InlineData("before/unstarted-brace}/{valid}/end")]
[InlineData("before/{missing}/end")]
[InlineData("before/{nested}/end")]
[InlineData("before/{int}/end")]
[InlineData("before/{repeated}/end")]
[InlineData("before/{map}/end")]
public void InvalidPattern(string pattern)
{
Assert.Throws<ArgumentException>(() => HttpRulePathPattern.Parse(pattern, RuleTestRequest.Descriptor));
}

}

private static HttpRulePathPattern ParsePattern(string pattern) =>
HttpRulePathPattern.Parse(pattern, RuleTestRequest.Descriptor);
Expand Down
95 changes: 95 additions & 0 deletions Google.Api.Gax.Grpc.Tests/Rest/HttpRuleTranscoderTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright 2022 Google LLC
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file or at
* https://developers.google.com/open-source/licenses/bsd
*/

using Google.Api.Gax.Grpc.Tests;
using Google.Protobuf;
using Google.Protobuf.Reflection;
using Google.Protobuf.WellKnownTypes;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Xunit;
using Xunit.Abstractions;
using static Google.Api.Gax.Grpc.Rest.Tests.Test;

namespace Google.Api.Gax.Grpc.Rest.Tests;

public class HttpRuleTranscoderTest
{
private static TestFile s_testFile = LoadTestFile();
public static IEnumerable<object[]> Tests => s_testFile.Tests
.Select(t => new object[] { new SerializableTest(t) });

private static TestFile LoadTestFile()
{
// TODO: Adjust when we move the tests to the google-cloud-conformance repo
var rootDirectory = ConformanceTestData.FindRepositoryRootDirectory();
var path = Path.Combine(rootDirectory, "Google.Api.Gax.Grpc.Tests", "Rest", "transcoder_tests.json");
string json = File.ReadAllText(path);
return TestFile.Parser.ParseJson(json);
}

[Theory]
[MemberData(nameof(Tests))]
public void Transcode(SerializableTest testWrapper)
{
var test = testWrapper.Test;
var rule = test.RuleCase switch
{
RuleOneofCase.InlineRule => test.InlineRule,
RuleOneofCase.RuleName => s_testFile.NamedRules[test.RuleName],
_ => throw new ArgumentException("No rule specified")
};

var requestMessageDescriptor = TranscoderTestReflection.Descriptor.FindTypeByName<MessageDescriptor>(test.RequestMessageName);
var request = test.Request is null ? null : JsonParser.Default.Parse(test.Request.ToString(), requestMessageDescriptor);

switch (test.ExpectedResultCase)
{
case ExpectedResultOneofCase.InvalidRule:
Assert.Throws<ArgumentException>(CreateTranscoder);
break;
case ExpectedResultOneofCase.NonmatchingRequest:
Assert.NotNull(request);
Assert.Null(CreateTranscoder().Transcode(request));
break;
case ExpectedResultOneofCase.Success:
Assert.NotNull(request);
var actualResult = CreateTranscoder().Transcode(request);
var expectedResult = test.Success;
Assert.Equal(expectedResult.Method, actualResult.Method.Method);
Assert.Equal(expectedResult.Uri, actualResult.RelativeUri);
var actualBody = actualResult.Body is null ? null : Struct.Parser.ParseJson(actualResult.Body);
Assert.Equal(expectedResult.Body, actualBody);
break;
default:
throw new ArgumentException($"Unknown expected result case: {test.ExpectedResultCase}");
}

HttpRuleTranscoder CreateTranscoder() => new HttpRuleTranscoder(test.Name, requestMessageDescriptor, rule);
}
}

public class SerializableTest : IXunitSerializable
{
public Test Test { get; private set; }

// Used in deserialization
public SerializableTest() { }

public SerializableTest(Test test) => Test = test;

public void Deserialize(IXunitSerializationInfo info) =>
Test = Test.Parser.ParseFrom(info.GetValue<byte[]>(nameof(Test)));

public void Serialize(IXunitSerializationInfo info) =>
info.AddValue(nameof(Test), Test.ToByteArray());

public override string ToString() => Test?.Name ?? "(Unknown)";
}

Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@
* https://developers.google.com/open-source/licenses/bsd
*/

using Google.Api.Gax.Grpc.Rest;
using Google.Protobuf.WellKnownTypes;
using Google.Rpc;
using System.Net;
using System.Net.Http;
using Xunit;
using gc = Grpc.Core;

namespace Google.Api.Gax.Grpc.Tests.Rest
namespace Google.Api.Gax.Grpc.Rest.Tests
{
public class ReadHttpResponseMessageTest
{
Expand Down

0 comments on commit 0d20c00

Please sign in to comment.