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

Allow generating test stubs separately from the client implementation #402

Merged
merged 3 commits into from
Mar 15, 2019

Conversation

mpetrov
Copy link
Contributor

@mpetrov mpetrov commented Mar 12, 2019

This allows generating test stubs in a separate file. Generating an implementation can be disabled with
--swiftgrpc_opt=Imlpementations=false.

A new ExtraModuleImports option allows adding import statement for the implementation. This enables generating test stubs in a separate module from implementation files. The name of the implementation module might very depending on the build system, so it can't be reliably inferred.

One use case for this would be generating different bazel build targets: a client target that's usable in production, and a stub target that's testonly = True (and only usable from test targets).

For example, a client could be generated in a echo_swift_grpc_client module follows:

protoc Sources/Examples/Echo/echo.proto \
--proto_path=Sources/Examples/Echo \
--plugin=.build/debug/protoc-gen-swift \
--plugin=.build/debug/protoc-gen-swiftgrpc \
--swiftgrpc_out=/tmp/echo_swift_grpc_client \
--swiftgrpc_opt=Client=true \
--swiftgrpc_opt=Server=false \
--swiftgrpc_opt=Visibility=Public

With a separate module for test stubs:

protoc Sources/Examples/Echo/echo.proto \
--proto_path=Sources/Examples/Echo \
--plugin=.build/debug/protoc-gen-swift \
--plugin=.build/debug/protoc-gen-swiftgrpc\
--swiftgrpc_out=/tmp/echo_swift_grpc_stub \
--swiftgrpc_opt=TestStubs=true \
--swiftgrpc_opt=Imlpementations=false \
--swiftgrpc_opt=Client=true \
--swiftgrpc_opt=Server=false \
--swiftgrpc_opt=ExtraModuleImports=echo_swift_grpc_client

@MrMage
Copy link
Collaborator

MrMage commented Mar 13, 2019

Sorry, it's still unclear to me how the logic has changed, especially with regards to how it used to behave depending on the Client/Server/TestStubs flags and how it behaves now. Would you mind elaborating further? It also looks like your changes only affect the generated code for the client, not the server? Should this be done for both?

@mpetrov
Copy link
Contributor Author

mpetrov commented Mar 13, 2019

There are effectively 3 changes here:

  1. Rather than interleaving generated clients and stubs, this separates them into two distinct sections of the generated source file. The primary goal of this is to allow turning either client generation or stub generation off with a single options check rather than sprinkling around if/else checks throughout the client generator source code. This is similar to how printNIOGRPCClient only gets called if options.generateNIOImplementation is true, rather than adding if options.generateNIOImplementation { ... } checks all throughout the generator.
  2. Allow injecting import statements into the generated source via the ExtraModuleImports option. This is to allow splitting generated source into various modules that can import each other. It would also help handle any weirdnesses specific to a particular build system that might come up.
  3. Generate client stubs when TestStubs=true is on, even if client code isn't included Client=false. I don't think I was fully factoring server-test stubs here, so this is probably the most half-baked part of this PR. I could alternatively (a) Introduce a Implementation=false flag to turn off actual implementation code OR (b) Introduce TestClientStubs and TestServerStubs options to allow finer-grained control. Explicitly setting TestStubs could modify both for backwards compatibility.

Feedback is welcome. I'm happy to farm off smaller PRs if you feel any parts of this are less controversial, or would make it easier to review.

@MrMage
Copy link
Collaborator

MrMage commented Mar 14, 2019

@mpetrov thank you for the elaboration! 1. and 2. sound reasonable to me. Same for 3., but I agree that we should have more granular options for this.

Sounds like we have four different binary flags here, but not all combinations would make sense, for example this one:

Client Server
Impls
TestStubs

So far, I tend towards having the following options:

  • Impls (naming to be discussed): Defaults to true. If set, the regular interfaces implementations will be generated.
  • TestStubs: If set, the test stubs implementations will be generated. Can not stand on its own without the interfaces generated by Impls; that's where ExtraModuleImports comes in.
  • Client/Server: Whether implementations/stubs should be generated for client, server, both, or none (with the last option not being very useful, same for Impls = false combined with TestStubs = false).

So these would be the conditions to generate the corresponding code:

Client Server
Impls Client && Impls Server && Impls
TestStubs Client && TestStubs Server && TestStubs

Thoughts?

mpetrov added a commit to mpetrov/grpc-swift that referenced this pull request Mar 14, 2019
This will make it simpler to conditionally turn of generating the
implementation, so that test stubs can be compiled separately. See
PR grpc#402 for more discussion.
@mpetrov
Copy link
Contributor Author

mpetrov commented Mar 14, 2019

Thanks for the feedback @MrMage

I split out the client generator updates (1.) in #403 since that's the bulk of the deltas in this PR.

An Impls or Implementations flag sounds good to me. I don't have strong preferences on naming, so happy to take suggestions.

MrMage pushed a commit that referenced this pull request Mar 14, 2019
This will make it simpler to conditionally turn of generating the
implementation, so that test stubs can be compiled separately. See
PR #402 for more discussion.
@MrMage
Copy link
Collaborator

MrMage commented Mar 14, 2019

@rebello95 any preference on naming? Otherwise, I would suggest Implementations. (@mpetrov, don't forget to document the flag in README.md.)

@mpetrov
Copy link
Contributor Author

mpetrov commented Mar 14, 2019

Rebased to include #403 and add new option.

README.md Outdated Show resolved Hide resolved
Sources/protoc-gen-swiftgrpc/Generator.swift Outdated Show resolved Hide resolved
Sources/protoc-gen-swiftgrpc/Generator.swift Outdated Show resolved Hide resolved
Sources/protoc-gen-swiftgrpc/options.swift Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
Copy link
Collaborator

@MrMage MrMage left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for these changes! I'll merge once @rebello95 has taken a look as well.

Copy link
Collaborator

@rebello95 rebello95 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding these options is a useful change overall, some comments

README.md Outdated
@@ -102,8 +102,10 @@ separated from the output directory by a colon.
| `Client` | `true`/`false` | `true` | Whether to generate client code |
| `Async` | `true`/`false` | `true` | Whether to generate asynchronous code |
| `Sync` | `true`/`false` | `true` | Whether to generate synchronous code |
| `Implementations` | `true`/`false` | `true` | Whether to generate protocols and non-test service code. Toggling this to `false` is mostly useful when combined with `TestStubs=true` to generate files containing only test stub code. |
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Other options don't have a trailing period in their description

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed trailing periods.

README.md Outdated
| `TestStubs` | `true`/`false` | `false` | Whether to generate test stub code |
| `FileNaming` | `FullPath`/`PathToUnderscores`/`DropPath` | `FullPath` | How to handle the naming of generated sources |
| `ExtraModuleImports` | `String` | `` | Extra module to import in generated code |
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The description is Extra module to import in generated code, but I assume multiple imports can be passed? If so, can we provide details on how that works?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the text a bit. Imports could look as follows:
--swiftgrpc_opt=ExtraModuleImports=foo,ExtraModuleImports=qux --swiftgrpc_opt=ExtraModuleImports=baz

Sources/protoc-gen-swiftgrpc/Generator-Client.swift Outdated Show resolved Hide resolved
@@ -88,7 +88,7 @@ class Generator {
"SwiftGRPC",
"SwiftProtobuf"]
}
for moduleName in moduleNames {
for moduleName in moduleNames + options.extraModuleImports {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be nice to sort these

@@ -118,6 +131,9 @@ class Generator {
if options.generateTestStubs && options.generateNIOImplementation {
fatalError("Generating test stubs is not yet supported for SwiftGRPC-NIO.")
}
if options.generateTestStubs && !options.generateImplementations {
fatalError("Generating server test stubs without an implementation is not yet supported.")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we plan to support this? If not, maybe remove "yet"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed for now.

@@ -131,6 +140,13 @@ final class GeneratorOptions {
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
}

case "ExtraModuleImports":
if !pair.value.isEmpty {
extraModuleImports.append(pair.value)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to split the value here to handle multiple import statements? I.e., pair.value.components(separatedBy: ",")?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, per other comment multiple imports would be done by repeating the parameter.

--swiftgrpc_opt=ExtraModuleImports=foo,ExtraModuleImports=qux --swiftgrpc_opt=ExtraModuleImports=baz

The options are already comma separated in parseParameter(string:), and changing the semantics there could break existing callers.

@mpetrov
Copy link
Contributor Author

mpetrov commented Mar 14, 2019

Took another pass, thanks for the feedback!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants