mirror of https://github.com/grpc/grpc.git
Initial version of Grpc.Tools & Msbuild integration tests (#31638)
* Inital Grpc.Tools unit tests
* refining the tests
* Get tests to run on Linux
* further grpc.tools tests changes
* changes for relative/absoulte paths
* Simplify running on just one framework
* Add Protobuf.MSBuild.dll for tests to use
* fix typo in comment
* fix copyright note
* Revert "Add Protobuf.MSBuild.dll for tests to use"
This reverts commit 542756cade
.
* cleanup Directory.Build.props
* fix location of tasksAssembly
* cleanup in preparing paths
* add test-out to .gitignore
* many simplifications to MsBuildIntegrationTest
* update expected test results data
* many improvements to fakeprotoc.py
* yapf code
* fix copyright licenses
* reintroduce normalized paths
* minor fixes
* attempt simplify Directory.Build.targets
* newline at EOF
* simplify result comparison
* add a TODO
* further simplify MsBuildIntegrationTest test harness
* avoid internal symbol conflict by diambiguating test protos
* add TODO
* style fixes
* one more fixup
Co-authored-by: Jan Tattermusch <jtattermusch@google.com>
pull/32047/head
parent
06faf44d0b
commit
0ff115e49c
27 changed files with 1433 additions and 3 deletions
@ -0,0 +1,27 @@ |
||||
<!-- |
||||
This overrides the Directory.Build.props in src/csharp/ |
||||
since we want different settings for the integration tests |
||||
--> |
||||
<Project> |
||||
|
||||
<PropertyGroup> |
||||
<LangVersion>8.0</LangVersion> |
||||
<TargetFramework>netstandard2.0</TargetFramework> |
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> |
||||
<SignAssembly>false</SignAssembly> |
||||
</PropertyGroup> |
||||
|
||||
<!-- These would normally be imported from the Grpc.Tools NuGet package --> |
||||
<Import Project="$(GrpcToolsBuildDir)\_grpc\_Grpc.Tools.props"/> |
||||
<Import Project="$(GrpcToolsBuildDir)\_protobuf\Google.Protobuf.Tools.props"/> |
||||
|
||||
<!-- Common properties for tests. --> |
||||
<!-- The <Protobuf_...> properties would normally be set when the files are imported |
||||
via the NuGet package. |
||||
--> |
||||
<PropertyGroup> |
||||
<Protobuf_Generator>CSharp</Protobuf_Generator> |
||||
<Protobuf_IntermediatePath>$(BaseIntermediateOutputPath)Debug/netstandard2.0</Protobuf_IntermediatePath> |
||||
</PropertyGroup> |
||||
|
||||
</Project> |
@ -0,0 +1,11 @@ |
||||
<!-- |
||||
This overrides the Directory.Build.targets in src/csharp/ |
||||
since we want different settings for the integration tests, particularly |
||||
we don't want to sign assemblies |
||||
--> |
||||
<Project> |
||||
|
||||
<!-- These would normally be imported from the Grpc.Tools NuGet package --> |
||||
<Import Project="$(GrpcToolsBuildDir)\_grpc\_Grpc.Tools.targets"/> |
||||
<Import Project="$(GrpcToolsBuildDir)\_protobuf\Google.Protobuf.Tools.targets"/> |
||||
</Project> |
@ -0,0 +1,35 @@ |
||||
// Copyright 2022 The gRPC Authors |
||||
// |
||||
// Licensed under the Apache License, Version 2.0 (the "License"); |
||||
// you may not use this file except in compliance with the License. |
||||
// You may obtain a copy of the License at |
||||
// |
||||
// http://www.apache.org/licenses/LICENSE-2.0 |
||||
// |
||||
// Unless required by applicable law or agreed to in writing, software |
||||
// distributed under the License is distributed on an "AS IS" BASIS, |
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
// See the License for the specific language governing permissions and |
||||
// limitations under the License. |
||||
|
||||
syntax = "proto3"; |
||||
|
||||
package grpc_tools_tests.integration_tests.test_at_in_path; |
||||
|
||||
// The greeting service definition. |
||||
service Greeter { |
||||
// Sends a greeting |
||||
rpc SayHello (HelloRequest) returns (HelloReply) {} |
||||
rpc SayHelloSlowly (HelloRequest) returns (HelloReply) {} |
||||
} |
||||
|
||||
// The request message containing the user's name. |
||||
message HelloRequest { |
||||
string name = 1; |
||||
int32 delay = 2; |
||||
} |
||||
|
||||
// The response message containing the greetings |
||||
message HelloReply { |
||||
string message = 1; |
||||
} |
@ -0,0 +1,30 @@ |
||||
#region Copyright notice and license |
||||
|
||||
// Copyright 2022 The gRPC Authors |
||||
// |
||||
// Licensed under the Apache License, Version 2.0 (the "License"); |
||||
// you may not use this file except in compliance with the License. |
||||
// You may obtain a copy of the License at |
||||
// |
||||
// http://www.apache.org/licenses/LICENSE-2.0 |
||||
// |
||||
// Unless required by applicable law or agreed to in writing, software |
||||
// distributed under the License is distributed on an "AS IS" BASIS, |
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
// See the License for the specific language governing permissions and |
||||
// limitations under the License. |
||||
|
||||
#endregion |
||||
|
||||
using System; |
||||
|
||||
namespace TestApp |
||||
{ |
||||
public class Program |
||||
{ |
||||
public static void Main(string[] args) |
||||
{ |
||||
Console.WriteLine("test app"); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,31 @@ |
||||
{ |
||||
"Files": { |
||||
"@protos/file.proto": { |
||||
"--csharp_out": [ |
||||
"${TEST_OUT_DIR}/obj/Debug/netstandard2.0/@protos" |
||||
], |
||||
"--plugin": [ |
||||
"protoc-gen-grpc=dummy-plugin-not-used" |
||||
], |
||||
"--grpc_out": [ |
||||
"${TEST_OUT_DIR}/obj/Debug/netstandard2.0/@protos" |
||||
], |
||||
"--proto_path": [ |
||||
"../../../Grpc.Tools/build/native/include", |
||||
"." |
||||
], |
||||
"--dependency_out": [ |
||||
"REGEX:${TEST_OUT_DIR}/obj/Debug/netstandard2.0/.*_file.protodep" |
||||
], |
||||
"--error_format": [ |
||||
"msvs" |
||||
], |
||||
"protofile": [ |
||||
"@protos/file.proto" |
||||
] |
||||
} |
||||
}, |
||||
"Metadata": { |
||||
"timestamp": "IGNORE:" |
||||
} |
||||
} |
@ -0,0 +1,13 @@ |
||||
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
||||
<!-- |
||||
Note: common properties and targets for tests are imported from the |
||||
Directory.Build.props and Directory.Build.targets files in the parent directoty |
||||
--> |
||||
|
||||
<!-- The protobuf compiler settings to test --> |
||||
<ItemGroup> |
||||
<Protobuf Include="**/*.proto" /> |
||||
</ItemGroup> |
||||
|
||||
</Project> |
@ -0,0 +1,30 @@ |
||||
#region Copyright notice and license |
||||
|
||||
// Copyright 2022 The gRPC Authors |
||||
// |
||||
// Licensed under the Apache License, Version 2.0 (the "License"); |
||||
// you may not use this file except in compliance with the License. |
||||
// You may obtain a copy of the License at |
||||
// |
||||
// http://www.apache.org/licenses/LICENSE-2.0 |
||||
// |
||||
// Unless required by applicable law or agreed to in writing, software |
||||
// distributed under the License is distributed on an "AS IS" BASIS, |
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
// See the License for the specific language governing permissions and |
||||
// limitations under the License. |
||||
|
||||
#endregion |
||||
|
||||
using System; |
||||
|
||||
namespace TestApp |
||||
{ |
||||
public class Program |
||||
{ |
||||
public static void Main(string[] args) |
||||
{ |
||||
Console.WriteLine("test app"); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,79 @@ |
||||
{ |
||||
"Files": { |
||||
"file.proto": { |
||||
"--csharp_out": [ |
||||
"${TEST_OUT_DIR}/obj/Debug/netstandard2.0" |
||||
], |
||||
"--plugin": [ |
||||
"protoc-gen-grpc=dummy-plugin-not-used" |
||||
], |
||||
"--grpc_out": [ |
||||
"${TEST_OUT_DIR}/obj/Debug/netstandard2.0" |
||||
], |
||||
"--proto_path": [ |
||||
"../../../Grpc.Tools/build/native/include", |
||||
"." |
||||
], |
||||
"--dependency_out": [ |
||||
"REGEX:${TEST_OUT_DIR}/obj/Debug/netstandard2.0/.*_file.protodep" |
||||
], |
||||
"--error_format": [ |
||||
"msvs" |
||||
], |
||||
"protofile": [ |
||||
"file.proto" |
||||
] |
||||
}, |
||||
"protos/another.proto": { |
||||
"--csharp_out": [ |
||||
"${TEST_OUT_DIR}/obj/Debug/netstandard2.0/protos" |
||||
], |
||||
"--plugin": [ |
||||
"protoc-gen-grpc=dummy-plugin-not-used" |
||||
], |
||||
"--grpc_out": [ |
||||
"${TEST_OUT_DIR}/obj/Debug/netstandard2.0/protos" |
||||
], |
||||
"--proto_path": [ |
||||
"../../../Grpc.Tools/build/native/include", |
||||
"." |
||||
], |
||||
"--dependency_out": [ |
||||
"REGEX:${TEST_OUT_DIR}/obj/Debug/netstandard2.0/.*_another.protodep" |
||||
], |
||||
"--error_format": [ |
||||
"msvs" |
||||
], |
||||
"protofile": [ |
||||
"protos/another.proto" |
||||
] |
||||
}, |
||||
"second.proto": { |
||||
"--csharp_out": [ |
||||
"${TEST_OUT_DIR}/obj/Debug/netstandard2.0" |
||||
], |
||||
"--plugin": [ |
||||
"protoc-gen-grpc=dummy-plugin-not-used" |
||||
], |
||||
"--grpc_out": [ |
||||
"${TEST_OUT_DIR}/obj/Debug/netstandard2.0" |
||||
], |
||||
"--proto_path": [ |
||||
"../../../Grpc.Tools/build/native/include", |
||||
"." |
||||
], |
||||
"--dependency_out": [ |
||||
"REGEX:${TEST_OUT_DIR}/obj/Debug/netstandard2.0/.*_second.protodep" |
||||
], |
||||
"--error_format": [ |
||||
"msvs" |
||||
], |
||||
"protofile": [ |
||||
"second.proto" |
||||
] |
||||
} |
||||
}, |
||||
"Metadata": { |
||||
"timestamp": "IGNORE:" |
||||
} |
||||
} |
@ -0,0 +1,35 @@ |
||||
// Copyright 2022 The gRPC Authors |
||||
// |
||||
// Licensed under the Apache License, Version 2.0 (the "License"); |
||||
// you may not use this file except in compliance with the License. |
||||
// You may obtain a copy of the License at |
||||
// |
||||
// http://www.apache.org/licenses/LICENSE-2.0 |
||||
// |
||||
// Unless required by applicable law or agreed to in writing, software |
||||
// distributed under the License is distributed on an "AS IS" BASIS, |
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
// See the License for the specific language governing permissions and |
||||
// limitations under the License. |
||||
|
||||
syntax = "proto3"; |
||||
|
||||
package grpc_tools_tests.integration_tests.test_multiple_protos; |
||||
|
||||
// The greeting service definition. |
||||
service Greeter { |
||||
// Sends a greeting |
||||
rpc SayHello (HelloRequest) returns (HelloReply) {} |
||||
rpc SayHelloSlowly (HelloRequest) returns (HelloReply) {} |
||||
} |
||||
|
||||
// The request message containing the user's name. |
||||
message HelloRequest { |
||||
string name = 1; |
||||
int32 delay = 2; |
||||
} |
||||
|
||||
// The response message containing the greetings |
||||
message HelloReply { |
||||
string message = 1; |
||||
} |
@ -0,0 +1,13 @@ |
||||
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
||||
<!-- |
||||
Note: common properties and targets for tests are imported from the |
||||
Directory.Build.props and Directory.Build.targets files in the parent directoty |
||||
--> |
||||
|
||||
<!-- The protobuf compiler settings to test --> |
||||
<ItemGroup> |
||||
<Protobuf Include="**/*.proto" /> |
||||
</ItemGroup> |
||||
|
||||
</Project> |
@ -0,0 +1,35 @@ |
||||
// Copyright 2022 The gRPC Authors |
||||
// |
||||
// Licensed under the Apache License, Version 2.0 (the "License"); |
||||
// you may not use this file except in compliance with the License. |
||||
// You may obtain a copy of the License at |
||||
// |
||||
// http://www.apache.org/licenses/LICENSE-2.0 |
||||
// |
||||
// Unless required by applicable law or agreed to in writing, software |
||||
// distributed under the License is distributed on an "AS IS" BASIS, |
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
// See the License for the specific language governing permissions and |
||||
// limitations under the License. |
||||
|
||||
syntax = "proto3"; |
||||
|
||||
package grpc_tools_tests.integration_tests.test_multiple_protos.another; |
||||
|
||||
// The greeting service definition. |
||||
service Greeter { |
||||
// Sends a greeting |
||||
rpc SayHello (HelloRequest) returns (HelloReply) {} |
||||
rpc SayHelloSlowly (HelloRequest) returns (HelloReply) {} |
||||
} |
||||
|
||||
// The request message containing the user's name. |
||||
message HelloRequest { |
||||
string name = 1; |
||||
int32 delay = 2; |
||||
} |
||||
|
||||
// The response message containing the greetings |
||||
message HelloReply { |
||||
string message = 1; |
||||
} |
@ -0,0 +1,35 @@ |
||||
// Copyright 2022 The gRPC Authors |
||||
// |
||||
// Licensed under the Apache License, Version 2.0 (the "License"); |
||||
// you may not use this file except in compliance with the License. |
||||
// You may obtain a copy of the License at |
||||
// |
||||
// http://www.apache.org/licenses/LICENSE-2.0 |
||||
// |
||||
// Unless required by applicable law or agreed to in writing, software |
||||
// distributed under the License is distributed on an "AS IS" BASIS, |
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
// See the License for the specific language governing permissions and |
||||
// limitations under the License. |
||||
|
||||
syntax = "proto3"; |
||||
|
||||
package grpc_tools_tests.integration_tests.test_multiple_protos.second; |
||||
|
||||
// The greeting service definition. |
||||
service Greeter { |
||||
// Sends a greeting |
||||
rpc SayHello (HelloRequest) returns (HelloReply) {} |
||||
rpc SayHelloSlowly (HelloRequest) returns (HelloReply) {} |
||||
} |
||||
|
||||
// The request message containing the user's name. |
||||
message HelloRequest { |
||||
string name = 1; |
||||
int32 delay = 2; |
||||
} |
||||
|
||||
// The response message containing the greetings |
||||
message HelloReply { |
||||
string message = 1; |
||||
} |
@ -0,0 +1,31 @@ |
||||
// Copyright 2022 The gRPC Authors |
||||
// |
||||
// Licensed under the Apache License, Version 2.0 (the "License"); |
||||
// you may not use this file except in compliance with the License. |
||||
// You may obtain a copy of the License at |
||||
// |
||||
// http://www.apache.org/licenses/LICENSE-2.0 |
||||
// |
||||
// Unless required by applicable law or agreed to in writing, software |
||||
// distributed under the License is distributed on an "AS IS" BASIS, |
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
// See the License for the specific language governing permissions and |
||||
// limitations under the License. |
||||
|
||||
syntax = "proto3"; |
||||
|
||||
option csharp_namespace = "GrpcGreeter"; |
||||
|
||||
package grpc_tools_tests.integration_tests.test_proto_outside_project; |
||||
|
||||
service Greeter { |
||||
rpc SayHello (Greet.HelloRequest) returns (Greet.HelloReply); |
||||
} |
||||
|
||||
message HelloRequest { |
||||
string name = 1; |
||||
} |
||||
|
||||
message HelloReply { |
||||
string message = 1; |
||||
} |
@ -0,0 +1,30 @@ |
||||
#region Copyright notice and license |
||||
|
||||
// Copyright 2022 The gRPC Authors |
||||
// |
||||
// Licensed under the Apache License, Version 2.0 (the "License"); |
||||
// you may not use this file except in compliance with the License. |
||||
// You may obtain a copy of the License at |
||||
// |
||||
// http://www.apache.org/licenses/LICENSE-2.0 |
||||
// |
||||
// Unless required by applicable law or agreed to in writing, software |
||||
// distributed under the License is distributed on an "AS IS" BASIS, |
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
// See the License for the specific language governing permissions and |
||||
// limitations under the License. |
||||
|
||||
#endregion |
||||
|
||||
using System; |
||||
|
||||
namespace TestApp |
||||
{ |
||||
public class Program |
||||
{ |
||||
public static void Main(string[] args) |
||||
{ |
||||
Console.WriteLine("test app"); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,34 @@ |
||||
{ |
||||
"Files": { |
||||
"../api/greet.proto": { |
||||
"--csharp_out": [ |
||||
"${TEST_OUT_DIR}/generated" |
||||
], |
||||
"--plugin": [ |
||||
"protoc-gen-grpc=dummy-plugin-not-used" |
||||
], |
||||
"--grpc_out": [ |
||||
"${TEST_OUT_DIR}/generated" |
||||
], |
||||
"--grpc_opt": [ |
||||
"no_server" |
||||
], |
||||
"--proto_path": [ |
||||
"../../../../Grpc.Tools/build/native/include", |
||||
"../api" |
||||
], |
||||
"--dependency_out": [ |
||||
"REGEX:${TEST_OUT_DIR}/obj/Debug/netstandard2.0/.*_greet.protodep" |
||||
], |
||||
"--error_format": [ |
||||
"msvs" |
||||
], |
||||
"protofile": [ |
||||
"../api/greet.proto" |
||||
] |
||||
} |
||||
}, |
||||
"Metadata": { |
||||
"timestamp": "IGNORE:" |
||||
} |
||||
} |
@ -0,0 +1,31 @@ |
||||
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
||||
<!-- |
||||
Note: common properties and targets for tests are imported from the |
||||
Directory.Build.props and Directory.Build.targets files in the parent directoty |
||||
--> |
||||
|
||||
<!-- The protobuf compiler settings to test --> |
||||
|
||||
<!-- From issue 29161 --> |
||||
<PropertyGroup> |
||||
<!-- full path to parent directory --> |
||||
<ParentDir>$(MSBuildThisFileDirectory)..\</ParentDir> |
||||
<ProtoApiRepoBaseDir>$(ParentDir)api\</ProtoApiRepoBaseDir> |
||||
<AutoGenOutputBaseDir>$(TestOutDir)\generated\</AutoGenOutputBaseDir> |
||||
</PropertyGroup> |
||||
|
||||
<ItemGroup> |
||||
|
||||
<Protobuf Include = "$(ProtoApiRepoBaseDir)\**\*.proto" |
||||
Link = "ProtoApi\%(RecursiveDir)%(Filename)%(Extension)" |
||||
Access = "Public" |
||||
ProtoCompile = "True" |
||||
ProtoRoot = "$(ProtoApiRepoBaseDir)" |
||||
CompileOutputs = "true" |
||||
OutputDir = "$(AutoGenOutputBaseDir)" |
||||
GrpcOutputDir = "$(AutoGenOutputBaseDir)" |
||||
GrpcServices = "client" /> |
||||
</ItemGroup> |
||||
|
||||
</Project> |
@ -0,0 +1,30 @@ |
||||
#region Copyright notice and license |
||||
|
||||
// Copyright 2022 The gRPC Authors |
||||
// |
||||
// Licensed under the Apache License, Version 2.0 (the "License"); |
||||
// you may not use this file except in compliance with the License. |
||||
// You may obtain a copy of the License at |
||||
// |
||||
// http://www.apache.org/licenses/LICENSE-2.0 |
||||
// |
||||
// Unless required by applicable law or agreed to in writing, software |
||||
// distributed under the License is distributed on an "AS IS" BASIS, |
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
// See the License for the specific language governing permissions and |
||||
// limitations under the License. |
||||
|
||||
#endregion |
||||
|
||||
using System; |
||||
|
||||
namespace TestApp |
||||
{ |
||||
public class Program |
||||
{ |
||||
public static void Main(string[] args) |
||||
{ |
||||
Console.WriteLine("test app"); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,31 @@ |
||||
{ |
||||
"Files": { |
||||
"file.proto": { |
||||
"--csharp_out": [ |
||||
"${TEST_OUT_DIR}/obj/Debug/netstandard2.0" |
||||
], |
||||
"--plugin": [ |
||||
"protoc-gen-grpc=dummy-plugin-not-used" |
||||
], |
||||
"--grpc_out": [ |
||||
"${TEST_OUT_DIR}/obj/Debug/netstandard2.0" |
||||
], |
||||
"--proto_path": [ |
||||
"../../../Grpc.Tools/build/native/include", |
||||
"." |
||||
], |
||||
"--dependency_out": [ |
||||
"REGEX:${TEST_OUT_DIR}/obj/Debug/netstandard2.0/.*_file.protodep" |
||||
], |
||||
"--error_format": [ |
||||
"msvs" |
||||
], |
||||
"protofile": [ |
||||
"file.proto" |
||||
] |
||||
} |
||||
}, |
||||
"Metadata": { |
||||
"timestamp": "IGNORE:" |
||||
} |
||||
} |
@ -0,0 +1,35 @@ |
||||
// Copyright 2022 The gRPC Authors |
||||
// |
||||
// Licensed under the Apache License, Version 2.0 (the "License"); |
||||
// you may not use this file except in compliance with the License. |
||||
// You may obtain a copy of the License at |
||||
// |
||||
// http://www.apache.org/licenses/LICENSE-2.0 |
||||
// |
||||
// Unless required by applicable law or agreed to in writing, software |
||||
// distributed under the License is distributed on an "AS IS" BASIS, |
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
// See the License for the specific language governing permissions and |
||||
// limitations under the License. |
||||
|
||||
syntax = "proto3"; |
||||
|
||||
package grpc_tools_tests.integration_tests.test_single_proto; |
||||
|
||||
// The greeting service definition. |
||||
service Greeter { |
||||
// Sends a greeting |
||||
rpc SayHello (HelloRequest) returns (HelloReply) {} |
||||
rpc SayHelloSlowly (HelloRequest) returns (HelloReply) {} |
||||
} |
||||
|
||||
// The request message containing the user's name. |
||||
message HelloRequest { |
||||
string name = 1; |
||||
int32 delay = 2; |
||||
} |
||||
|
||||
// The response message containing the greetings |
||||
message HelloReply { |
||||
string message = 1; |
||||
} |
@ -0,0 +1,13 @@ |
||||
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
||||
<!-- |
||||
Note: common properties and targets for tests are imported from the |
||||
Directory.Build.props and Directory.Build.targets files in the parent directoty |
||||
--> |
||||
|
||||
<!-- The protobuf compiler settings to test --> |
||||
<ItemGroup> |
||||
<Protobuf Include="**/*.proto" /> |
||||
</ItemGroup> |
||||
|
||||
</Project> |
@ -0,0 +1,438 @@ |
||||
#region Copyright notice and license |
||||
|
||||
// Copyright 2022 The gRPC Authors |
||||
// |
||||
// Licensed under the Apache License, Version 2.0 (the "License"); |
||||
// you may not use this file except in compliance with the License. |
||||
// You may obtain a copy of the License at |
||||
// |
||||
// http://www.apache.org/licenses/LICENSE-2.0 |
||||
// |
||||
// Unless required by applicable law or agreed to in writing, software |
||||
// distributed under the License is distributed on an "AS IS" BASIS, |
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
// See the License for the specific language governing permissions and |
||||
// limitations under the License. |
||||
|
||||
#endregion |
||||
|
||||
using System; |
||||
using System.IO; |
||||
using NUnit.Framework; |
||||
using System.Diagnostics; |
||||
using System.Reflection; |
||||
using System.Collections.Specialized; |
||||
using System.Collections; |
||||
using System.Collections.Generic; |
||||
using System.Text.RegularExpressions; |
||||
using Newtonsoft.Json; |
||||
|
||||
namespace Grpc.Tools.Tests |
||||
{ |
||||
/// <summary> |
||||
/// Tests for Grpc.Tools MSBuild .target and .props files. |
||||
/// </summary> |
||||
/// <remarks> |
||||
/// The Grpc.Tools NuGet package is not tested directly, but instead the |
||||
/// same .target and .props files are included in a MSBuild project and |
||||
/// that project is built using "dotnet build" with the SDK installed on |
||||
/// the test machine. |
||||
/// <para> |
||||
/// The real protoc compiler is not called. Instead a fake protoc script is |
||||
/// called that does the minimum work needed for the build to succeed |
||||
/// (generating cs files and writing dependencies file) and also writes out |
||||
/// the arguments it was called with in a JSON file. The output is checked |
||||
/// with expected results. |
||||
/// </para> |
||||
/// </remarks> |
||||
[TestFixture] |
||||
public class MsBuildIntegrationTest |
||||
{ |
||||
private const string TASKS_ASSEMBLY_PROPERTY = "_Protobuf_MsBuildAssembly"; |
||||
private const string TASKS_ASSEMBLY_DLL = "Protobuf.MSBuild.dll"; |
||||
private const string PROTBUF_FULLPATH_PROPERTY = "Protobuf_ProtocFullPath"; |
||||
private const string PLUGIN_FULLPATH_PROPERTY = "gRPC_PluginFullPath"; |
||||
private const string TOOLS_BUILD_DIR_PROPERTY = "GrpcToolsBuildDir"; |
||||
|
||||
private const string MSBUILD_LOG_VERBOSITY = "diagnostic"; // "diagnostic" or "detailed" |
||||
|
||||
private string testId; |
||||
private string fakeProtoc; |
||||
private string grpcToolsBuildDir; |
||||
private string tasksAssembly; |
||||
private string testDataDir; |
||||
private string testProjectDir; |
||||
private string testOutBaseDir; |
||||
private string testOutDir; |
||||
|
||||
[SetUp] |
||||
public void InitTest() |
||||
{ |
||||
#if NET45 |
||||
// We need to run these tests for one framework. |
||||
// This test class is just a driver for calling the |
||||
// "dotnet build" processes, so it doesn't matter what |
||||
// the runtime of this class actually is. |
||||
Assert.Ignore("Skipping test when NET45"); |
||||
#endif |
||||
} |
||||
|
||||
[Test] |
||||
public void TestSingleProto() |
||||
{ |
||||
SetUpForTest(nameof(TestSingleProto)); |
||||
|
||||
var expectedFiles = new ExpectedFilesBuilder(); |
||||
expectedFiles.Add("file.proto", "File.cs", "FileGrpc.cs"); |
||||
|
||||
TryRunMsBuild("TestSingleProto", expectedFiles.ToString()); |
||||
} |
||||
|
||||
[Test] |
||||
public void TestMultipleProtos() |
||||
{ |
||||
SetUpForTest(nameof(TestMultipleProtos)); |
||||
|
||||
var expectedFiles = new ExpectedFilesBuilder(); |
||||
// TODO(jtattermusch): add test that "duplicate" .proto file |
||||
// name (under different directories) is allowed. See https://github.com/grpc/grpc/issues/17672 |
||||
expectedFiles.Add("file.proto", "File.cs", "FileGrpc.cs") |
||||
.Add("protos/another.proto", "Another.cs", "AnotherGrpc.cs") |
||||
.Add("second.proto", "Second.cs", "SecondGrpc.cs"); |
||||
|
||||
TryRunMsBuild("TestMultipleProtos", expectedFiles.ToString()); |
||||
} |
||||
|
||||
[Test] |
||||
public void TestAtInPath() |
||||
{ |
||||
SetUpForTest(nameof(TestAtInPath)); |
||||
|
||||
var expectedFiles = new ExpectedFilesBuilder(); |
||||
expectedFiles.Add("@protos/file.proto", "File.cs", "FileGrpc.cs"); |
||||
|
||||
TryRunMsBuild("TestAtInPath", expectedFiles.ToString()); |
||||
} |
||||
|
||||
[Test] |
||||
public void TestProtoOutsideProject() |
||||
{ |
||||
SetUpForTest(nameof(TestProtoOutsideProject), "TestProtoOutsideProject/project"); |
||||
|
||||
var expectedFiles = new ExpectedFilesBuilder(); |
||||
expectedFiles.Add("../api/greet.proto", "Greet.cs", "GreetGrpc.cs"); |
||||
|
||||
TryRunMsBuild("TestProtoOutsideProject/project", expectedFiles.ToString()); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Set up common paths for all the tests |
||||
/// </summary> |
||||
private void SetUpCommonPaths() |
||||
{ |
||||
var assemblyDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); |
||||
testDataDir = Path.GetFullPath($"{assemblyDir}/../../../IntegrationTests"); |
||||
|
||||
// Path for fake proto. |
||||
// On Windows we have to wrap the python script in a BAT script since we can only |
||||
// pass one executable name without parameters to the MSBuild |
||||
// - e.g. we can't give "python fakeprotoc.py" |
||||
var fakeProtocScript = Platform.IsWindows ? "fakeprotoc.bat" : "fakeprotoc.py"; |
||||
fakeProtoc = Path.GetFullPath($"{assemblyDir}/../../../scripts/{fakeProtocScript}"); |
||||
|
||||
// Path for "build" directory under Grpc.Tools |
||||
grpcToolsBuildDir = Path.GetFullPath($"{assemblyDir}/../../../../Grpc.Tools/build"); |
||||
|
||||
// Task assembly is needed to run the extension tasks |
||||
// We use the assembly that was copied next to Grpc.Tools.Tests.dll |
||||
// as a Grpc.Tools.Tests dependency since we know it's the correct one |
||||
// and we don't have to figure out its original path (which is different |
||||
// for debug/release builds etc). |
||||
tasksAssembly = Path.Combine(assemblyDir, TASKS_ASSEMBLY_DLL); |
||||
|
||||
// put test ouptput directory outside of Grpc.Tools.Tests to avoid problems with |
||||
// repeated builds. |
||||
testOutBaseDir = NormalizePath(Path.GetFullPath($"{assemblyDir}/../../../../test-out/grpc_tools_integration_tests")); |
||||
} |
||||
|
||||
|
||||
/// <summary> |
||||
/// Normalize path string to use just forward slashes. That makes it easier to compare paths |
||||
/// for equality in the tests. |
||||
/// </summary> |
||||
private string NormalizePath(string path) |
||||
{ |
||||
return path.Replace('\\','/'); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Set up test specific paths |
||||
/// </summary> |
||||
/// <param name="testName">Name of the test</param> |
||||
/// <param name="testPath">Optional path to the test project</param> |
||||
private void SetUpForTest(string testName, string testPath = null) |
||||
{ |
||||
if (testPath == null) { |
||||
testPath = testName; |
||||
} |
||||
|
||||
SetUpCommonPaths(); |
||||
|
||||
testId = $"{testName}_run-{Guid.NewGuid().ToString()}"; |
||||
Console.WriteLine($"TestID for test: {testId}"); |
||||
|
||||
// Paths for test data |
||||
testProjectDir = NormalizePath(Path.Combine(testDataDir, testPath)); |
||||
testOutDir = NormalizePath(Path.Combine(testOutBaseDir, testId)); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Run "dotnet build" on the test's project file. |
||||
/// </summary> |
||||
/// <param name="testName">Name of test and name of directory containing the test</param> |
||||
/// <param name="filesToGenerate">Tell the fake protoc script which files to generate</param> |
||||
/// <param name="testId">A unique ID for the test run - used to create results file</param> |
||||
private void TryRunMsBuild(string testName, string filesToGenerate) |
||||
{ |
||||
Directory.CreateDirectory(testOutDir); |
||||
|
||||
// create the arguments for the "dotnet build" |
||||
var args = $"build -p:{TASKS_ASSEMBLY_PROPERTY}={tasksAssembly}" |
||||
+ $" -p:TestOutDir={testOutDir}" |
||||
+ $" -p:BaseOutputPath={testOutDir}/bin/" |
||||
+ $" -p:BaseIntermediateOutputPath={testOutDir}/obj/" |
||||
+ $" -p:{TOOLS_BUILD_DIR_PROPERTY}={grpcToolsBuildDir}" |
||||
+ $" -p:{PROTBUF_FULLPATH_PROPERTY}={fakeProtoc}" |
||||
+ $" -p:{PLUGIN_FULLPATH_PROPERTY}=dummy-plugin-not-used" |
||||
+ $" -fl -flp:LogFile={testOutDir}/log/msbuild.log;verbosity={MSBUILD_LOG_VERBOSITY}" |
||||
+ $" msbuildtest.csproj"; |
||||
|
||||
// To pass additional parameters to fake protoc process |
||||
// we need to use environment variables |
||||
var envVariables = new StringDictionary { |
||||
{ "FAKEPROTOC_PROJECTDIR", testProjectDir }, |
||||
{ "FAKEPROTOC_OUTDIR", testOutDir }, |
||||
{ "FAKEPROTOC_GENERATE_EXPECTED", filesToGenerate }, |
||||
{ "FAKEPROTOC_TESTID", testId } |
||||
}; |
||||
|
||||
// Run the "dotnet build" |
||||
ProcessMsbuild(args, testProjectDir, envVariables); |
||||
|
||||
// Check the results JSON matches the expected JSON |
||||
Results actualResults = Results.Read(testOutDir + "/log/results.json"); |
||||
Results expectedResults = Results.Read(testProjectDir + "/expected.json"); |
||||
CompareResults(expectedResults, actualResults); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Run the "dotnet build" command |
||||
/// </summary> |
||||
/// <param name="args">arguments to the dotnet command</param> |
||||
/// <param name="workingDirectory">working directory</param> |
||||
/// <param name="envVariables">environment variables to set</param> |
||||
private void ProcessMsbuild(string args, string workingDirectory, StringDictionary envVariables) |
||||
{ |
||||
using (var process = new Process()) |
||||
{ |
||||
process.StartInfo.FileName = "dotnet"; |
||||
process.StartInfo.Arguments = args; |
||||
process.StartInfo.RedirectStandardOutput = true; |
||||
process.StartInfo.RedirectStandardError = true; |
||||
process.StartInfo.WorkingDirectory = workingDirectory; |
||||
process.StartInfo.UseShellExecute = false; |
||||
StringDictionary procEnv = process.StartInfo.EnvironmentVariables; |
||||
foreach (DictionaryEntry entry in envVariables) |
||||
{ |
||||
if (!procEnv.ContainsKey((string)entry.Key)) |
||||
{ |
||||
procEnv.Add((string)entry.Key, (string)entry.Value); |
||||
} |
||||
} |
||||
|
||||
process.OutputDataReceived += (sender, e) => { |
||||
if (e.Data != null) |
||||
{ |
||||
Console.WriteLine(e.Data); |
||||
} |
||||
}; |
||||
process.ErrorDataReceived += (sender, e) => { |
||||
if (e.Data != null) |
||||
{ |
||||
Console.WriteLine(e.Data); |
||||
} |
||||
}; |
||||
|
||||
process.Start(); |
||||
|
||||
process.BeginErrorReadLine(); |
||||
process.BeginOutputReadLine(); |
||||
|
||||
process.WaitForExit(); |
||||
Assert.AreEqual(0, process.ExitCode, "The dotnet/msbuild subprocess invocation exited with non-zero exitcode."); |
||||
} |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Compare the JSON results to the expected results |
||||
/// </summary> |
||||
/// <param name="expected"></param> |
||||
/// <param name="actual"></param> |
||||
private void CompareResults(Results expected, Results actual) |
||||
{ |
||||
// Check set of .proto files processed is the same |
||||
var protofiles = expected.ProtoFiles; |
||||
CollectionAssert.AreEquivalent(protofiles, actual.ProtoFiles, "Set of .proto files being processed must match."); |
||||
|
||||
// check protoc arguments |
||||
foreach (string protofile in protofiles) |
||||
{ |
||||
var expectedArgs = expected.GetArgumentNames(protofile); |
||||
var actualArgs = actual.GetArgumentNames(protofile); |
||||
CollectionAssert.AreEquivalent(expectedArgs, actualArgs, $"Set of protoc arguments used for {protofile} must match."); |
||||
|
||||
// Check the values. |
||||
// Any value with: |
||||
// - IGNORE: - will not be compared but must exist |
||||
// - REGEX: - compare using a regular expression |
||||
// - anything else is an exact match |
||||
// Expected results can also have tokens that are replaced before comparing: |
||||
// - ${TEST_OUT_DIR} - the test output directory |
||||
foreach (string argname in expectedArgs) |
||||
{ |
||||
var expectedValues = expected.GetArgumentValues(protofile, argname); |
||||
var actualValues = actual.GetArgumentValues(protofile, argname); |
||||
|
||||
Assert.AreEqual(expectedValues.Count, actualValues.Count, |
||||
$"{protofile}: Wrong number of occurrences of argument '{argname}'"); |
||||
|
||||
// Since generally the order of arguments on the commandline is important, |
||||
// it is fair to compare arguments with expected values one by one. |
||||
// Most arguments are only used at most once by the msbuild integration anyway. |
||||
for (int i = 0; i < expectedValues.Count; i++) |
||||
{ |
||||
var expectedValue = ReplaceTokens(expectedValues[i]); |
||||
var actualValue = actualValues[i]; |
||||
|
||||
if (expectedValue.StartsWith("IGNORE:")) |
||||
continue; |
||||
|
||||
var regexPrefix = "REGEX:"; |
||||
if (expectedValue.StartsWith(regexPrefix)) |
||||
{ |
||||
string pattern = expectedValue.Substring(regexPrefix.Length); |
||||
Assert.IsTrue(Regex.IsMatch(actualValue, pattern), |
||||
$"{protofile}: Expected value '{expectedValue}' for argument '{argname}'. Actual value: '{actualValue}'"); |
||||
} |
||||
else |
||||
{ |
||||
Assert.AreEqual(expectedValue, actualValue, $"{protofile}: Wrong value for argument '{argname}'"); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
private string ReplaceTokens(string original) |
||||
{ |
||||
return original |
||||
.Replace("${TEST_OUT_DIR}", testOutDir); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Helper class for formatting the string specifying the list of proto files and |
||||
/// the expected generated files for each proto file. |
||||
/// </summary> |
||||
public class ExpectedFilesBuilder |
||||
{ |
||||
private readonly List<string> protoAndFiles = new List<string>(); |
||||
|
||||
public ExpectedFilesBuilder Add(string protoFile, params string[] files) |
||||
{ |
||||
protoAndFiles.Add(protoFile + ":" + string.Join(";", files)); |
||||
return this; |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
return string.Join("|", protoAndFiles.ToArray()); |
||||
} |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Hold the JSON results |
||||
/// </summary> |
||||
public class Results |
||||
{ |
||||
/// <summary> |
||||
/// JSON "Metadata" |
||||
/// </summary> |
||||
public Dictionary<string, string> Metadata { get; set; } |
||||
|
||||
/// <summary> |
||||
/// JSON "Files" |
||||
/// </summary> |
||||
public Dictionary<string, Dictionary<string, List<string>>> Files { get; set; } |
||||
|
||||
/// <summary> |
||||
/// Read a JSON file |
||||
/// </summary> |
||||
/// <param name="filepath"></param> |
||||
/// <returns></returns> |
||||
public static Results Read(string filepath) |
||||
{ |
||||
using (StreamReader file = File.OpenText(filepath)) |
||||
{ |
||||
JsonSerializer serializer = new JsonSerializer(); |
||||
Results results = (Results)serializer.Deserialize(file, typeof(Results)); |
||||
return results; |
||||
} |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Get the proto file names from the JSON |
||||
/// </summary> |
||||
public SortedSet<string> ProtoFiles => new SortedSet<string>(Files.Keys); |
||||
|
||||
/// <summary> |
||||
/// Get the protoc arguments for the associated proto file |
||||
/// </summary> |
||||
/// <param name="protofile"></param> |
||||
/// <returns></returns> |
||||
public SortedSet<string> GetArgumentNames(string protofile) |
||||
{ |
||||
Dictionary<string, List<string>> args; |
||||
if (Files.TryGetValue(protofile, out args)) |
||||
{ |
||||
return new SortedSet<string>(args.Keys); |
||||
} |
||||
else |
||||
{ |
||||
return new SortedSet<string>(); |
||||
} |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Get the values for the named argument for the proto file |
||||
/// </summary> |
||||
/// <param name="protofile">proto file</param> |
||||
/// <param name="name">argument</param> |
||||
/// <returns></returns> |
||||
public List<string> GetArgumentValues(string protofile, string name) |
||||
{ |
||||
Dictionary<string, List<string>> args; |
||||
if (Files.TryGetValue(protofile, out args)) |
||||
{ |
||||
List<string> values; |
||||
if (args.TryGetValue(name, out values)) |
||||
{ |
||||
return new List<string>(values); |
||||
} |
||||
} |
||||
return new List<string>(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
} |
@ -0,0 +1,18 @@ |
||||
@rem Copyright 2022 The gRPC Authors |
||||
@rem |
||||
@rem Licensed under the Apache License, Version 2.0 (the "License"); |
||||
@rem you may not use this file except in compliance with the License. |
||||
@rem You may obtain a copy of the License at |
||||
@rem |
||||
@rem http://www.apache.org/licenses/LICENSE-2.0 |
||||
@rem |
||||
@rem Unless required by applicable law or agreed to in writing, software |
||||
@rem distributed under the License is distributed on an "AS IS" BASIS, |
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
@rem See the License for the specific language governing permissions and |
||||
@rem limitations under the License. |
||||
|
||||
@echo off |
||||
set script_dir=%~dp0. |
||||
python.exe "%script_dir%\fakeprotoc.py" %* |
||||
exit /B %errorlevel% |
@ -0,0 +1,353 @@ |
||||
#!/usr/bin/env python3 |
||||
# Copyright 2022 The gRPC Authors |
||||
# |
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
# |
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
|
||||
# Fake protobuf compiler for use in the Grpc.Tools MSBuild integration |
||||
# unit tests. Its purpose is to be called from the Grpc.Tools |
||||
# Google.Protobuf.Tools.targets MSBuild file instead of the actual protoc |
||||
# compiler. This script: |
||||
# - parses the command line arguments |
||||
# - generates expected dependencies file |
||||
# - generates dummy .cs files that are expected by the tests |
||||
# - writes a JSON results file containing the arguments passed in |
||||
|
||||
# Configuration is done via environment variables as it is not possible |
||||
# to pass additional argument when called from the MSBuild scripts under test. |
||||
# |
||||
# Environment variables: |
||||
# FAKEPROTOC_PROJECTDIR - project directory |
||||
# FAKEPROTOC_OUTDIR - output directory for generated files and output file |
||||
# FAKEPROTOC_GENERATE_EXPECTED - list of expected generated files in format: |
||||
# file1.proto:csfile1.cs;csfile2.cs|file2.proto:csfile3.cs;csfile4.cs|... |
||||
|
||||
import datetime |
||||
import hashlib |
||||
import json |
||||
import os |
||||
import sys |
||||
|
||||
# Set to True to write out debug messages from this script |
||||
_dbg = True |
||||
# file to which write the debug log |
||||
_dbgfile = None |
||||
|
||||
|
||||
def _open_debug_log(filename): |
||||
"""Create debug file for this script.""" |
||||
global _dbgfile |
||||
if _dbg: |
||||
# append mode since this script may be called multiple times |
||||
# during one build/test |
||||
_dbgfile = open(filename, "a") |
||||
|
||||
|
||||
def _close_debug_log(): |
||||
"""Close the debug log file.""" |
||||
if _dbgfile: |
||||
_dbgfile.close() |
||||
|
||||
|
||||
def _write_debug(msg): |
||||
"""Write to the debug log file if debug is enabled.""" |
||||
if _dbg and _dbgfile: |
||||
print(msg, file=_dbgfile, flush=True) |
||||
|
||||
|
||||
def _read_protoc_arguments(): |
||||
""" |
||||
Get the protoc argument from the command line and |
||||
any response files specified on the command line. |
||||
|
||||
Returns the list of arguments. |
||||
""" |
||||
_write_debug("\nread_protoc_arguments") |
||||
result = [] |
||||
for arg in sys.argv[1:]: |
||||
_write_debug(" arg: " + arg) |
||||
if arg.startswith("@"): |
||||
# TODO(jtattermusch): inserting a "commented out" argument feels hacky |
||||
result.append("# RSP file: %s" % arg) |
||||
rsp_file_name = arg[1:] |
||||
result.extend(_read_rsp_file(rsp_file_name)) |
||||
else: |
||||
result.append(arg) |
||||
return result |
||||
|
||||
|
||||
def _read_rsp_file(rspfile): |
||||
""" |
||||
Returns list of arguments from a response file. |
||||
""" |
||||
_write_debug("\nread_rsp_file: " + rspfile) |
||||
result = [] |
||||
with open(rspfile, "r") as rsp: |
||||
for line in rsp: |
||||
line = line.strip() |
||||
_write_debug(" line: " + line) |
||||
result.append(line) |
||||
return result |
||||
|
||||
|
||||
def _parse_protoc_arguments(protoc_args, projectdir): |
||||
""" |
||||
Parse the protoc arguments from the provided list |
||||
""" |
||||
|
||||
_write_debug("\nparse_protoc_arguments") |
||||
arg_dict = {} |
||||
for arg in protoc_args: |
||||
_write_debug("Parsing: %s" % arg) |
||||
|
||||
# All arguments containing file or directory paths are |
||||
# normalized by converting all '\' and changed to '/' |
||||
if arg.startswith("--"): |
||||
# Assumes that cmdline arguments are always passed in the |
||||
# "--somearg=argvalue", which happens to be the form that |
||||
# msbuild integration uses, but it's not the only way. |
||||
(name, value) = arg.split("=", 1) |
||||
|
||||
if name == "--dependency_out" or name == "--grpc_out" or name == "--csharp_out": |
||||
# For args that contain a path, make the path absolute and normalize it |
||||
# to make it easier to assert equality in tests. |
||||
value = _normalized_absolute_path(value) |
||||
|
||||
if name == "--proto_path": |
||||
# for simplicity keep this one as relative path rather than absolute path |
||||
# since it is an input file that is always be near the project file |
||||
value = _normalized_relative_to_projectdir(value, projectdir) |
||||
|
||||
_add_protoc_arg_to_dict(arg_dict, name, value) |
||||
|
||||
elif arg.startswith("#"): |
||||
pass # ignore |
||||
else: |
||||
# arg represents a proto file name |
||||
arg = _normalized_relative_to_projectdir(arg, projectdir) |
||||
_add_protoc_arg_to_dict(arg_dict, "protofile", arg) |
||||
return arg_dict |
||||
|
||||
|
||||
def _add_protoc_arg_to_dict(arg_dict, name, value): |
||||
""" |
||||
Add the arguments with name/value to a multi-dictionary of arguments |
||||
""" |
||||
if name not in arg_dict: |
||||
arg_dict[name] = [] |
||||
|
||||
arg_dict[name].append(value) |
||||
|
||||
|
||||
def _normalized_relative_to_projectdir(file, projectdir): |
||||
"""Convert a file path to one relative to the project directory.""" |
||||
try: |
||||
return _normalize_slashes( |
||||
os.path.relpath(os.path.abspath(file), projectdir)) |
||||
except ValueError: |
||||
# On Windows if the paths are on different drives then we get this error |
||||
# Just return the absolute path |
||||
return _normalize_slashes(os.path.abspath(file)) |
||||
|
||||
|
||||
def _normalized_absolute_path(file): |
||||
"""Returns normalized absolute path to file.""" |
||||
return _normalize_slashes(os.path.abspath(file)) |
||||
|
||||
|
||||
def _normalize_slashes(path): |
||||
"""Change all backslashes to forward slashes to make comparing path strings easier.""" |
||||
return path.replace("\\", "/") |
||||
|
||||
|
||||
def _write_or_update_results_json(log_dir, protofile, protoc_arg_dict): |
||||
""" Write or update the results JSON file """ |
||||
|
||||
# Read existing json. |
||||
# Since protoc may be called more than once each build/test if there is |
||||
# more than one protoc file, we read the existing data to add to it. |
||||
fname = os.path.abspath("%s/results.json" % log_dir) |
||||
if os.path.isfile(fname): |
||||
# Load the original contents. |
||||
with open(fname, "r") as forig: |
||||
results_json = json.load(forig) |
||||
else: |
||||
results_json = {} |
||||
results_json['Files'] = {} |
||||
|
||||
results_json['Files'][protofile] = protoc_arg_dict |
||||
results_json["Metadata"] = {"timestamp": str(datetime.datetime.now())} |
||||
|
||||
with open(fname, "w") as fout: |
||||
json.dump(results_json, fout, indent=4) |
||||
|
||||
|
||||
def _parse_generate_expected(generate_expected_str): |
||||
""" |
||||
Parse FAKEPROTOC_GENERATE_EXPECTED that specifies the proto files |
||||
and the cs files to generate. We rely on the test to say what is |
||||
expected rather than trying to work it out in this script. |
||||
|
||||
The format of the input is: |
||||
file1.proto:csfile1.cs;csfile2.cs|file2.proto:csfile3.cs;csfile4.cs|... |
||||
""" |
||||
_write_debug("\nparse_generate_expected") |
||||
|
||||
result = {} |
||||
entries = generate_expected_str.split("|") |
||||
for entry in entries: |
||||
parts = entry.split(":") |
||||
pfile = _normalize_slashes(parts[0]) |
||||
csfiles = parts[1].split(";") |
||||
result[pfile] = csfiles |
||||
_write_debug(pfile + " : " + str(csfiles)) |
||||
return result |
||||
|
||||
|
||||
def _get_cs_files_to_generate(protofile, proto_to_generated): |
||||
"""Returns list of .cs files to generated based on FAKEPROTOC_GENERATE_EXPECTED env.""" |
||||
protoname_normalized = _normalize_slashes(protofile) |
||||
cs_files_to_generate = proto_to_generated.get(protoname_normalized) |
||||
return cs_files_to_generate |
||||
|
||||
|
||||
def _generate_cs_files(protofile, cs_files_to_generate, out_dir, projectdir): |
||||
"""Create expected cs files.""" |
||||
_write_debug("\ngenerate_cs_files") |
||||
|
||||
if not cs_files_to_generate: |
||||
_write_debug("No .cs files matching proto file name %s" % protofile) |
||||
return |
||||
|
||||
if not os.path.isabs(out_dir): |
||||
# if not absolute, it is relative to project directory |
||||
out_dir = os.path.abspath("%s/%s" % (projectdir, out_dir)) |
||||
|
||||
# Ensure out_dir exists |
||||
if not os.path.isdir(out_dir): |
||||
os.makedirs(out_dir) |
||||
|
||||
timestamp = str(datetime.datetime.now()) |
||||
for csfile in cs_files_to_generate: |
||||
csfile_fullpath = "%s/%s" % (out_dir, csfile) |
||||
_write_debug("Creating: %s" % csfile_fullpath) |
||||
with open(csfile_fullpath, "w") as fout: |
||||
print("// Generated by fake protoc: %s" % timestamp, file=fout) |
||||
|
||||
|
||||
def _create_dependency_file(protofile, cs_files_to_generate, dependencyfile, |
||||
grpcout): |
||||
"""Create the expected dependency file.""" |
||||
_write_debug("\ncreate_dependency_file") |
||||
|
||||
if not dependencyfile: |
||||
_write_debug("dependencyfile is not set.") |
||||
return |
||||
|
||||
if not cs_files_to_generate: |
||||
_write_debug("No .cs files matching proto file name %s" % protofile) |
||||
return |
||||
|
||||
_write_debug("Creating dependency file: %s" % dependencyfile) |
||||
with open(dependencyfile, "w") as out: |
||||
nfiles = len(cs_files_to_generate) |
||||
for i in range(0, nfiles): |
||||
cs_filename = os.path.join(grpcout, cs_files_to_generate[i]) |
||||
if i == nfiles - 1: |
||||
print("%s: %s" % (cs_filename, protofile), file=out) |
||||
else: |
||||
print("%s \\" % cs_filename, file=out) |
||||
|
||||
|
||||
def _getenv(name): |
||||
# Note there is a bug in .NET core 3.x that lowercases the environment |
||||
# variable names when they are added via Process.StartInfo, so we need to |
||||
# check both cases here (only an issue on Linux which is case sensitive) |
||||
value = os.getenv(name) |
||||
if value is None: |
||||
value = os.getenv(name.lower()) |
||||
return value |
||||
|
||||
|
||||
def main(): |
||||
# Check environment variables for the additional arguments used in the tests. |
||||
|
||||
projectdir = _getenv('FAKEPROTOC_PROJECTDIR') |
||||
if not projectdir: |
||||
print("FAKEPROTOC_PROJECTDIR not set") |
||||
sys.exit(1) |
||||
projectdir = os.path.abspath(projectdir) |
||||
|
||||
# Output directory for generated files and output file |
||||
protoc_outdir = _getenv('FAKEPROTOC_OUTDIR') |
||||
if not protoc_outdir: |
||||
print("FAKEPROTOC_OUTDIR not set") |
||||
sys.exit(1) |
||||
protoc_outdir = os.path.abspath(protoc_outdir) |
||||
|
||||
# Get list of expected generated files from env variable |
||||
generate_expected = _getenv('FAKEPROTOC_GENERATE_EXPECTED') |
||||
if not generate_expected: |
||||
print("FAKEPROTOC_GENERATE_EXPECTED not set") |
||||
sys.exit(1) |
||||
|
||||
# Prepare the debug log |
||||
log_dir = os.path.join(protoc_outdir, "log") |
||||
if not os.path.isdir(log_dir): |
||||
os.makedirs(log_dir) |
||||
_open_debug_log("%s/fakeprotoc_log.txt" % log_dir) |
||||
|
||||
_write_debug( |
||||
("##### fakeprotoc called at %s\n" + "FAKEPROTOC_PROJECTDIR = %s\n" + |
||||
"FAKEPROTOC_GENERATE_EXPECTED = %s\n") % |
||||
(datetime.datetime.now(), projectdir, generate_expected)) |
||||
|
||||
proto_to_generated = _parse_generate_expected(generate_expected) |
||||
protoc_args = _read_protoc_arguments() |
||||
protoc_arg_dict = _parse_protoc_arguments(protoc_args, projectdir) |
||||
|
||||
# If argument was passed multiple times, take the last occurrence of it. |
||||
# TODO(jtattermusch): handle multiple occurrences of the same argument |
||||
dependencyfile = protoc_arg_dict.get('--dependency_out')[-1] |
||||
grpcout = protoc_arg_dict.get('--grpc_out')[-1] |
||||
|
||||
if len(protoc_arg_dict.get('protofile')) != 1: |
||||
# regular protoc can process multiple .proto files passed at once, but we know |
||||
# the Grpc.Tools msbuild integration only ever passes one .proto file per invocation. |
||||
print( |
||||
"Expecting to get exactly one .proto file argument per fakeprotoc invocation." |
||||
) |
||||
sys.exit(1) |
||||
protofile = protoc_arg_dict.get('protofile')[0] |
||||
|
||||
cs_files_to_generate = _get_cs_files_to_generate( |
||||
protofile=protofile, proto_to_generated=proto_to_generated) |
||||
|
||||
_create_dependency_file(protofile=protofile, |
||||
cs_files_to_generate=cs_files_to_generate, |
||||
dependencyfile=dependencyfile, |
||||
grpcout=grpcout) |
||||
|
||||
_generate_cs_files(protofile=protofile, |
||||
cs_files_to_generate=cs_files_to_generate, |
||||
out_dir=grpcout, |
||||
projectdir=projectdir) |
||||
|
||||
_write_or_update_results_json(log_dir=log_dir, |
||||
protofile=protofile, |
||||
protoc_arg_dict=protoc_arg_dict) |
||||
|
||||
_close_debug_log() |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
main() |
Loading…
Reference in new issue