| # Build API Faux/Mock and Validation Call |
| |
| The faux module (so named to avoid confusion with the python `mock` library) |
| provides mock call functionality for the Build API. |
| The mock calls can feel abstract and even pointless when you're implementing |
| an endpoint, but have important long term implications. |
| Any given version of the Build API will be living for **years** in branches, |
| and ideally we'd like to know very quickly and easily if the ToT implementation |
| of the Build API consumers have functional implementations for whatever version |
| of the Build API is in the branch they're building. |
| The Build API's mock calls fill this role by providing static, valid responses |
| built into and branched with the API, so even as the ToT API and ToT API |
| consumers change, branches will always have the mock responses for their |
| version. |
| See the [Faux Motivation](#faux-motivation) section at the end for an example |
| of how it can help. |
| |
| |
| ## Overview |
| |
| The `faux` decorators service one or both of two cases; mock success and |
| mock error. |
| There is a third call type, mock validation failure, but it is handled |
| automatically for every endpoint. |
| In both the mock success and error cases, the response should be "shaped" |
| like a real response, but the data is fake, and things like files do not |
| need to actually exist. |
| For example, if a response includes a package, `foo/bar-1.0` (which does |
| not exist at all), `chromeos-base/chromeos-chrome-1.2.3.4` (which is a real |
| package with a fake version), and |
| `chromeos-base/chromeos-chrome-80.0.3987.5_rc-r1` (which is a real package |
| with a real version, but is old and no longer used) are all potential return |
| values. |
| The key point is that the responses are hard coded in the controllers so no |
| expensive operations ever need to be run. |
| This will allow mock builds to be executed in a matter of seconds and provide |
| targeted feedback on what implementations problems may exist without spending |
| hours on each build. |
| |
| Mock success responses should look like those you would expect from a normal |
| execution of the endpoint. |
| Mock error responses those from an execution that encountered a _significant_ |
| error. |
| Error responses frequently do not contain any data because an exception and |
| logs are often the primary means of communicating failures, all of which |
| exists outside of the response itself. |
| The `sysroot.install_packages` endpoint is an example that does utilize the |
| error response -- when a package fails to build, the package gets recorded |
| and included in the response, which the recipes then uses to populate data on |
| the builder overview page, so you can see what package failed without digging |
| through the logs. |
| |
| |
| ## Reference |
| |
| ### `@faux.success` & `@faux.error` |
| |
| The `success` and `error` decorators are the primary tools for populating |
| responses. |
| The decorators each take a function that populates the data for its |
| respective case. |
| The function needs to take the same arguments as an endpoint, but only ever |
| need to use the response. |
| |
| ```python |
| def _mock_endpoint_success(_input_proto, output_proto, _config_proto): |
| output_proto.success = 'Success :)' |
| |
| |
| def _mock_endpoint_failure(_input_proto, output_proto, _config_proto): |
| output_proto.failure = 'Error :(' |
| |
| |
| @faux.success(_mock_endpoint_success) |
| @faux.error(_mock_endpoint_failure) |
| def endpoint(input_proto, output_proto, _config_proto): |
| try: |
| do_thing() |
| output_proto.success = 'Success :)' |
| except Exception as e: |
| output_proto.failure = str(e) |
| ``` |
| |
| ### `@faux.empty_success` & `@faux.empty_error` |
| |
| The `empty_success` and `empty_error` decorators provide empty responses |
| when the endpoint's response contains no success/error data for their |
| respective cases. |
| They take no arguments. |
| |
| ```python |
| def _mock_endpoint_success(_input_proto, output_proto, _config_proto): |
| output_proto.success = 'Success :)' |
| |
| |
| @faux.success(_mock_endpoint_success) |
| @faux.empty_error |
| def endpoint(input_proto, output_proto, _config_proto): |
| do_thing() |
| output_proto.success = 'Success :)' |
| ``` |
| |
| ### `@faux.all_empty` |
| |
| The `all_empty` decorator is a convenience wrapper for using both |
| `empty_success` and `empty_error`. |
| This is typical for endpoints that execute functionality that produce no |
| new artifacts worth reporting, and do not have detailed error reporting |
| built into the endpoint. |
| |
| ```python |
| @faux.all_empty |
| def endpoint(input_proto, output_proto, config_proto): |
| do_thing() |
| ``` |
| |
| |
| ## Faux Motivation |
| |
| Branches are the component of the system that makes the faux module important. |
| We create branches of the code for every release, e.g. R90, R89, etc., as well |
| as branches for factory, firmware, and a few other things. |
| These branches need to function until their end of life. |
| Release branches generally live ~18 weeks (6 weeks each for dev, beta, stable). |
| More importantly, factory and firmware branches are for the lifetime of their |
| device. |
| |
| Builders always run the consumer code (recipes) from ToT, and we continue to |
| iterate on the Build API itself to make improvements and changes, which then |
| also means changes in recipes. |
| When recipes, at ToT, checks out a branch to build, the Build API on that branch |
| might be identical to ToT, but as they get older, that is less and less likely. |
| If every change is backwards compatible, we would never have problems, but |
| that simply isn't realistic in the long term. |
| The question for ToT recipes then becomes "Can I build this branch?" |
| |
| The goal of the faux module is to help answer that question. |
| Of particular value, answering that question *before* breaking changes land. |
| Without the faux module, the answer the that question is expensive, requiring |
| a complete build of every branch. |
| Additionally, the answer generated by a full build might be incorrect if the |
| branch has a bug or flake that causes builds to fail in a way unrelated to how |
| the endpoints are called or their responses handled. |
| The faux module allows answering that question using static responses that |
| require no computation to execute, so each branch can be checked on the order |
| of seconds rather than hours, making comprehensive presubmit checks possible. |
| |
| Note that faux calls only provide static outputs. |
| Answering the question "Can I build this branch?" also requires validating |
| the input sent to the API. |
| That piece is covered by using `--validate-only` calls, which validate the input |
| and then do not run the rest of the endpoint. |
| Those calls are handled by the [validate module](./validation.md). |