This README is a continuation of Hello World Tutorial Part 3, creating a “Hello world!” endpoint from scratch. This tutorial assumes you have already executed all the steps in Parts 1-3, and have all the code in place.
In this tutorial, we'll be adding mock calls in our endpoint using faux
decorators.
The faux
module implements the mock call functionality for the Build API. Our goal is to provide Build API consumers a quick and easy way to validate their implementations. By adding simple, static responses that have the correct “shape” (i.e. the data is all filled in but values like “foo” are fine), Build API consumers can verify their implementation without running full builds.
Before we talk about implementing the mock calls, we‘ll look at how to execute them. The config argument to the controllers is what controls the behavior. In general, endpoints shouldn’t need to use it directly, the decorators will be able to handle any case where it's needed. We do have to be able to set it to execute the functionality, though. The gen_call_scripts
workflow has arguments to set up the configs.
The important ones are --mock-success
, --mock-failure
, and --validate-only
. We'll be working with the mock arguments in this tutorial, but if you use the --validate-only
argument per the following instructions now and rerun the endpoint, you will see output identical to our final result in Part 3, but without the “Hello, moon!” line in the output!
To run one of the special call variants, simply pass the argument to gen_call_scripts
. You can only have one call variant generated at a time! The API can only generate one output at a time, so right now you must choose which mode you want to apply to all endpoint executions.
Since our current endpoint has no value to return, our work is very easy.
chromite/api/controller/hello.py
:
from chromite.api import faux from chromite.api import validate from chromite.lib import hello_lib @faux.all_empty @validate.require('target') @validate.validation_complete def Hello(request, response, config): hello_lib.hello(target=request.target)
The new decorator, @faux.all_empty
, simply ensures an empty response for both the success and error mock calls. Now let's run the mock call and see what we get.
$> cd ~/chromiumos/chromite/api/contrib/ $> ./gen_call_scripts --mock-success $> ./call_scripts/hello__hello Running chromite.api.HelloService/Hello 13:59:31: DEBUG: Services registered successfully. Completed chromite.api.HelloService/Hello Success! Return Code: 0 Result: {} $> ./gen_call_scripts --mock-failure $> ./call_scripts/hello__hello Running chromite.api.HelloService/Hello 14:02:56: DEBUG: Services registered successfully. Completed chromite.api.HelloService/Hello Return Code: 1 Result: {}
The mock success case looks just like the normal success output, except it is missing the “Hello, moon!” message we would normally see because we never actually executed the endpoint itself! The failure output we haven't seen yet, but the important distinctions to note are the lack of the “Success!” line and the Return Code
line having changed to non-zero.
Before we go further, let's update our endpoint to not just print our message, but also return it. Below is our full, new hello.proto
, with the added line commented.
infra/proto_branched/src/chromite/api/hello.proto
:
// Proto config. syntax = "proto3"; package chromite.api; option go_package = "go.chromium.org/chromiumos/infra/proto/go/chromite/api"; import "chromite/api/build_api.proto"; // HelloService/Hello request and response messages. message HelloRequest { string target = 1; } message HelloResponse { // NEW! string hello_message = 1; } service HelloService { option (service_options) = { module: "hello", }; rpc Hello(HelloRequest) returns (HelloResponse); }
Next we need to regenerate the protobuf bindings again.
$> cd ~/chromiumos/infra/proto_branched $> ./generate.sh $> cd ~/chromiumos/chromite/api $> ./compile_build_api_proto
We'll then return the message from our hello_lib.py
function.
chromite/lib/hello_lib.py
:
def hello(target): msg = f'Hello, {target}!' print(msg) return msg
Now we can set the value in the response.
chromite/api/controller/hello.py
:
@faux.all_empty @validate.require('target') @validate.validation_complete def Hello(request, response, config): hello_message = hello_lib.hello(target=request.target) response.hello_message = hello_message
Let's run the endpoint to make sure it works.
$> cd ~/chromiumos/chromite/api/contrib/ $> ./gen_call_scripts $> cd ./call_scripts/ $> echo '{"target": "moon"}' > hello__hello_input.json $> ./hello__hello Running chromite.api.HelloService/Hello 13:32:04: DEBUG: Services registered successfully. 13:32:04: DEBUG: Validating target is set. Hello, moon! Completed chromite.api.HelloService/Hello Success! Return Code: 0 Result: { "helloMessage": "Hello, moon!" }
The output json file is automatically printed in the call_scripts
output (the Result:
section), so we can see the output now includes our message!
As we just saw, our successful execution cases now include data in the output message, so let's update the mock success case to also return a message.
chromite/api/controller/hello.py
:
from chromite.api import faux from chromite.api import validate from chromite.lib import hello_lib def _hello_success(_request, response, _config): response.hello_message = 'Hello, world!' @faux.success(_hello_success) @faux.empty_error @validate.require('target') @validate.validation_complete def Hello(request, response, _config): hello_message = hello_lib.hello(target=request.target) response.hello_message = hello_message
You will notice the endpoint itself is untouched, except that have prefixed the config argument with an underscore. This convention is used throughout chromite to denote unused arguments, and is added now for reasons discussed below.
First, we replaced the @faux.all_empty
decorator with two new ones. As one might expect, the second (@faux.empty_error
) is the same failure case behavior as the @faux.all_empty
decorator. The success case, @faux.success
, and the new function, _hello_success
, are the bulk of our change.
The @faux.success
decorator (and each of its error counterparts) takes a function to execute when the mock call is executed. For our success case, we need to populate response.hello_message
, just like we did when we added the output. You‘ll notice these success and error functions always have underscores prefixing their input and config arguments. This is because we don’t care what the input is for our mock calls, and we never need to concern ourselves with the config values at the point where we're generating a mock response. For our mock success, our goal is just to fill out a static, valid output. So we just choose a message that looks sort of like something we might return normally, and hard code it in. For more information about the faux
decorators, see the faux reference.
Now let's try running it.
$> cd ~/chromiumos/chromite/api/contrib/ $> echo '{"target": "moon"}' > ./call_scripts/hello__hello_input.json $> ./gen_call_scripts $> echo "Regular execution to compare." $> ./call_scripts/hello__hello Running chromite.api.HelloService/Hello 14:35:27: DEBUG: Services registered successfully. 14:35:27: DEBUG: Validating target is set. Hello, moon! Completed chromite.api.HelloService/Hello Success! Return Code: 0 Result: { "helloMessage": "Hello, moon!" } $> ./gen_call_scripts --mock-success $> echo "Mock success execution." $> ./call_scripts/hello__hello Running chromite.api.HelloService/Hello 14:37:31: DEBUG: Services registered successfully. Completed chromite.api.HelloService/Hello Success! Return Code: 0 Result: { "helloMessage": "Hello, world!" }
We use the same input file in both calls, but as you can see by the output it is only used in the normal call. As we saw in the first example using @faux.all_empty
, we are also missing the printed message since we are not executing the endpoint itself, instead we're only executing our _hello_success
function, that only populates the output value with our static “Hello, world!” message.
Success!
We will not go through the exercise of adding error responses here as it is an identical process to the success case, except using different decorators. To do the exercise yourself, use the @faux.error
decorator instead of the @faux.empty_error
we used above, passing through your new error function like we did with the @faux.success
decorator. In practice, it is unlikely a success and error case would fill out the same output field, but for the purposes of a tutorial, it's just fine!