|
| 1 | +--- |
| 2 | +description: When working with or adding autests, the end to end tests for the project |
| 3 | +alwaysApply: false |
| 4 | +--- |
| 5 | +This rule describes writing of end to end autests |
| 6 | + |
| 7 | +Apache Traffic Server (ATS) has two types of tests: catch tests for unit tests and the autest framework for end-to-end testing. autests are declarative rather than imperative, using a specific Python framework syntax. Tests reside in `tests/gold_tests/` with `.test.py` extensions. For autest framework documentation, see: https://autestsuite.bitbucket.io/index.html |
| 8 | + |
| 9 | +File Structure and Naming: |
| 10 | +- Place tests in appropriate subdirectories under `tests/gold_tests/` (e.g., `cache/`, `pluginTest/<plugin_name>`, `tls/`, etc.) |
| 11 | +- Use descriptive names with `.test.py` extension (e.g., `cache-auth.test.py`, `stats_over_http.test.py`) |
| 12 | +- Organize related tests in feature-specific subdirectories |
| 13 | +- autest, a general testing framework, is extended to add ATS testing support via `.test.ext` files in `tests/gold_tests/autest-site` |
| 14 | + |
| 15 | +Running Autests: |
| 16 | +If ATS cmake build is properly configured, tests can be run with: |
| 17 | +```bash |
| 18 | +cmake --build build |
| 19 | +cmake --install build |
| 20 | +cd build/tests |
| 21 | +pipenv install |
| 22 | +./autest.sh --sandbox /tmp/sbcursor --clean=none -f <test_name_without_test_py_extension> |
| 23 | +``` |
| 24 | + |
| 25 | +For example, to run `cache-auth.test.py`: |
| 26 | +```bash |
| 27 | +./autest.sh --sandbox /tmp/sbcursor --clean=none -f cache-auth |
| 28 | +``` |
| 29 | + |
| 30 | +Test File Template Structure |
| 31 | + |
| 32 | +Always start a test file with: |
| 33 | + |
| 34 | +```python |
| 35 | +''' |
| 36 | +Brief description of what the test validates |
| 37 | +''' |
| 38 | +# Licensed to the Apache Software Foundation (ASF) under one |
| 39 | +# or more contributor license agreements. See the NOTICE file |
| 40 | +# distributed with this work for additional information |
| 41 | +# regarding copyright ownership. The ASF licenses this file |
| 42 | +# to you under the Apache License, Version 2.0 (the |
| 43 | +# "License"); you may not use this file except in compliance |
| 44 | +# with the License. You may obtain a copy of the License at |
| 45 | +# |
| 46 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 47 | +# |
| 48 | +# Unless required by applicable law or agreed to in writing, software |
| 49 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 50 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 51 | +# See the License for the specific language governing permissions and |
| 52 | +# limitations under the License. |
| 53 | +``` |
| 54 | + |
| 55 | +Test Configuration: |
| 56 | + |
| 57 | +Set global test properties: |
| 58 | + |
| 59 | +```python |
| 60 | +Test.Summary = 'Brief description of test purpose' |
| 61 | +Test.ContinueOnFail = True # Usually set to True for multi-step tests |
| 62 | +``` |
| 63 | + |
| 64 | + |
| 65 | +Class-Based Test Organization |
| 66 | + |
| 67 | +Prefer organizing tests as Python classes and use Python type hints: |
| 68 | + |
| 69 | +```python |
| 70 | +class YourTestClass: |
| 71 | + """ |
| 72 | + Docstring explaining the test purpose and any relevant documentation links |
| 73 | + """ |
| 74 | + |
| 75 | + def __init__(self): |
| 76 | + self._setupOriginServer() # or self.setupServers() |
| 77 | + self._setupTS() # or self.setupATS() |
| 78 | + |
| 79 | + def _setupOriginServer(self): |
| 80 | + # Create origin server(s) |
| 81 | + self._server = Test.MakeOriginServer("server") |
| 82 | + # OR for replay-based tests: |
| 83 | + # self.server = Test.MakeVerifierServerProcess("server-name", "replay/file.replay.yaml") |
| 84 | + |
| 85 | + def _setupTS(self): |
| 86 | + # Create ATS process |
| 87 | + self._ts = Test.MakeATSProcess("ts") |
| 88 | + |
| 89 | + # Configure records.config |
| 90 | + self._ts.Disk.records_config.update({ |
| 91 | + "proxy.config.diags.debug.enabled": 1, |
| 92 | + "proxy.config.diags.debug.tags": "relevant_debug_tags", |
| 93 | + # Add other configuration as needed |
| 94 | + }) |
| 95 | + |
| 96 | + # Configure remap.config if needed |
| 97 | + self._ts.Disk.remap_config.AddLine( |
| 98 | + f"map / http://127.0.0.1:{self.server.Variables.http_port}/" |
| 99 | + ) |
| 100 | + |
| 101 | + # Configure plugins if needed |
| 102 | + # self._ts.Disk.plugin_config.AddLine('plugin_name.so plugin_args') |
| 103 | + |
| 104 | + # Set up log validation if needed |
| 105 | + # self._ts.Disk.diags_log.Content += Testers.ContainsExpression( |
| 106 | + # "expected log message", "Description of what this validates" |
| 107 | + # ) |
| 108 | + |
| 109 | + def run(self): |
| 110 | + # Execute test scenarios |
| 111 | + self._testScenario1() |
| 112 | + self._testScenario2() |
| 113 | + # etc. |
| 114 | + |
| 115 | + def testScenario1(self): |
| 116 | + tr = Test.AddTestRun('Description of this test scenario') |
| 117 | + |
| 118 | + # Start processes |
| 119 | + tr.Processes.Default.StartBefore(self._server) |
| 120 | + tr.Processes.Default.StartBefore(self._ts) |
| 121 | + |
| 122 | + # Execute test command |
| 123 | + # Option 1: curl command |
| 124 | + tr.MakeCurlCommand(f"-vs --http1.1 http://127.0.0.1:{self._ts.Variables.port}/path", ts=self._ts) |
| 125 | + |
| 126 | + # Option 2: custom command |
| 127 | + # tr.Processes.Default.Command = "your_command_here" |
| 128 | + |
| 129 | + # Option 3: verifier client for replay tests |
| 130 | + # tr.AddVerifierClientProcess("client-name", "replay/file.replay.yaml", |
| 131 | + # http_ports=[self._ts.Variables.port]) |
| 132 | + |
| 133 | + # Set expectations |
| 134 | + tr.Processes.Default.ReturnCode = 0 |
| 135 | + tr.Processes.Default.Streams.stdout += Testers.ContainsExpression( |
| 136 | + 'expected_output', 'Description of what this validates' |
| 137 | + ) |
| 138 | + # Optionally compare output to a gold file. |
| 139 | + # tr.Processes.Default.Streams.stderr = "gold/expected_stderr.gold" |
| 140 | + tr.Processes.Default.TimeOut = 5 |
| 141 | + |
| 142 | + # Keep processes running for subsequent tests |
| 143 | + tr.StillRunningAfter = self._server |
| 144 | + tr.StillRunningAfter = self._ts |
| 145 | + |
| 146 | +# Execute the test |
| 147 | +YourTestClass().run() |
| 148 | +``` |
| 149 | + |
| 150 | +Key Framework Objects and Methods |
| 151 | + |
| 152 | +Test Configuration |
| 153 | +- `Test.Summary`: Brief test description |
| 154 | +- `Test.ContinueOnFail`: Whether to continue after failures |
| 155 | +- `Test.SkipUnless()`: Conditional test execution |
| 156 | +- `Test.AddTestRun()`: Create new test run. Each test contains one or more of these. |
| 157 | + |
| 158 | +Process Creation |
| 159 | +- `tr.MakeATSProcess` or `Test.MakeATSProcess("name")`: Create an ATS instance for a test run or a global one. |
| 160 | +- `tr.MakeOriginServer("name")` or `Test.MakeOriginServer("name")`: Create an origin server for a test run or a global one. |
| 161 | +- `tr.AddVerifierServerProcess` or `Test.MakeVerifierServerProcess("name", "replay_file")`: Create replay-based server |
| 162 | +- Note that every process, except for the Default process for each TestRun, takes a name and the name has to be unique across all Process names in the test.py file. |
| 163 | + |
| 164 | +Proxy Verifier Client and Server |
| 165 | +- `tr.MakeoriginServer` is the legacy server type. It just sets up a server. Typically client HTTP traffic is generated using curl. New tests should use `AddVerifierServerProcess` |
| 166 | +- `tr.AddVerifierServerProcess` is paired with `tr.AddVerifierClientProcess` which both use the same external replay config yaml file. These two processes then exchange traffic to test ATS per the replay yaml file that has verification rules. |
| 167 | +- For proxy-verifier documentation, see: https://github.com/yahoo/proxy-verifier |
| 168 | + |
| 169 | +Here's an example for how the server can be configured: |
| 170 | + |
| 171 | +```python |
| 172 | + def _configure_server(self, tr: 'TestRun'): |
| 173 | + """Configure the server. |
| 174 | + |
| 175 | + :param tr: The TestRun object to associate the server process with. |
| 176 | + """ |
| 177 | + server = tr.AddVerifierServerProcess(f"server_{Test_ip_allow.server_counter}", self.replay_file) |
| 178 | + Test_ip_allow.server_counter += 1 |
| 179 | + self._server = server |
| 180 | +``` |
| 181 | + |
| 182 | +Here's an example of how the client is configured: |
| 183 | + |
| 184 | +```python |
| 185 | + tr.Processes.Default.StartBefore(self._server) |
| 186 | + tr.Processes.Default.StartBefore(self._ts) |
| 187 | + |
| 188 | + tr.AddVerifierClientProcess( |
| 189 | + f'client-{Test_ip_allow.client_counter}', |
| 190 | + self.replay_file, |
| 191 | + http_ports=[self._ts.Variables.proxy_protocol_port], |
| 192 | + https_ports=[self._ts.Variables.ssl_port], |
| 193 | + http3_ports=[self._ts.Variables.ssl_port], |
| 194 | + keys=self.replay_keys) |
| 195 | +``` |
| 196 | + |
| 197 | +ATS Configuration |
| 198 | +- `ts.Disk.records_config.update`: Basic ATS configuration |
| 199 | +- `ts.Disk.remap_config.AddLine()`: Add a remap rule. |
| 200 | +- `ts.Disk.remap_config.AddLines()`: Add multiple remap rules. |
| 201 | +- `ts.Variables.port`, `ts.Variables.ssl_port`: Access ATS HTTP or HTTPS port |
| 202 | + |
| 203 | +ATS HTTPS Configuration |
| 204 | +Many tests require testing HTTPS. This requires some specific configuration: |
| 205 | +- `ts.addDefaultSSLFiles` Copy the default SSL certs for ATS to use. |
| 206 | +- `ts.Disk.ssl_multicert_config.AddLine("dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key")` configure ATS to use the certs |
| 207 | + |
| 208 | +Also for HTTP, add these configurations to the records_config.update: |
| 209 | + |
| 210 | +```python |
| 211 | +"proxy.config.ssl.server.cert.path": f"{self.ts.Variables.SSLDir}", |
| 212 | +"proxy.config.ssl.server.private_key.path": f"{self.ts.Variables.SSLDir}", |
| 213 | +``` |
| 214 | + |
| 215 | +Plugin testing considerations: |
| 216 | +Some tests verify plugin behavior. Follow these guidelines when testing a plugin: |
| 217 | +- `ts.Disk.plugin_config.AddLine()`: Configure plugins. This must be done for any plugin under test. |
| 218 | +- Add `Test.SkipUnless(Condition.PluginExists('plugin_name.so'))` at the top of the test to only run if the plugin is installed. |
| 219 | + |
| 220 | +Traffic Generation |
| 221 | +- `tr.Processes.Default.Command`: Set custom command. Sometimes a test has an adhoc Python script to generate traffic, for example. |
| 222 | +- `tr.MakeCurlCommand()`: Execute curl command. This sets tr.Processes.Default.Command to run a curl command. |
| 223 | +- `tr.AddVerifierClientProcess()`: This also sets tr.Processes.Default.Command to use the Proxy Verifier replay client to generate traffic. This takes, among other parameters, the name of a replay file. |
| 224 | +- `tr.Processes.Default.StartBefore()`: Start process before the client starts. This must be done for ats and all servers before the client starts. Although, if there are global servers, they can only be started once by the first Default process of the first TestRun. |
| 225 | + |
| 226 | +Validation |
| 227 | +- `Testers.ContainsExpression(pattern, description)`: Validate output contains pattern. Generally += these. |
| 228 | +- `tr.Processes.Default.ReturnCode`: Expected exit code. By default it is 0, but certain negative tests should expect 1. |
| 229 | +- `tr.Processes.Default.Streams.all`: Stream validation |
| 230 | +- `tr.Processes.Default.TimeOut`: Test timeout |
| 231 | +- `tr.StillRunningAfter`: Ensure the server is still running after the client finishes. |
| 232 | +- For VerifierClient and VerifierServer, much validation is done in the replay files themselves. The test.py should add output verification that each transaction happened as expected. |
| 233 | + |
| 234 | +Here's an example Verifier replay file: |
| 235 | + |
| 236 | +```yaml |
| 237 | +# Licensed to the Apache Software Foundation (ASF) under one |
| 238 | +# or more contributor license agreements. See the NOTICE file |
| 239 | +# distributed with this work for additional information |
| 240 | +# regarding copyright ownership. The ASF licenses this file |
| 241 | +# to you under the Apache License, Version 2.0 (the |
| 242 | +# "License"); you may not use this file except in compliance |
| 243 | +# with the License. You may obtain a copy of the License at |
| 244 | +# |
| 245 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 246 | +# |
| 247 | +# Unless required by applicable law or agreed to in writing, software |
| 248 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 249 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 250 | +# See the License for the specific language governing permissions and |
| 251 | +# limitations under the License. |
| 252 | + |
| 253 | +sessions: |
| 254 | + |
| 255 | +- transactions: |
| 256 | + |
| 257 | + - client-request: |
| 258 | + method: GET |
| 259 | + url: / |
| 260 | + version: '1.1' |
| 261 | + headers: |
| 262 | + fields: |
| 263 | + - [ Host, fqdn1 ] |
| 264 | + - [ uuid, 1 ] |
| 265 | + |
| 266 | + proxy-response: |
| 267 | + status: 200 |
| 268 | + headers: |
| 269 | + fields: |
| 270 | + - [ Content-Length, { value: 19, as: equal } ] |
| 271 | + - [ Cache-Control, { value: no-cache, as: equal } ] |
| 272 | + - [ Content-Type, { value: text/plain, as: equal } ] |
| 273 | + - [ Server, { value: ATS, as: contains } ] |
| 274 | + content: |
| 275 | + encoding: plain |
| 276 | + data: | |
| 277 | + small body content |
| 278 | + verify: { as: equal } |
| 279 | + |
| 280 | + - client-request: |
| 281 | + method: GET |
| 282 | + url: /path |
| 283 | + version: '1.1' |
| 284 | + headers: |
| 285 | + fields: |
| 286 | + - [ Host, fqdn1 ] |
| 287 | + - [ uuid, 2 ] |
| 288 | + |
| 289 | + proxy-response: |
| 290 | + status: 404 |
| 291 | + headers: |
| 292 | + fields: |
| 293 | + - [ Cache-Control, { value: no-store, as: equal } ] |
| 294 | + - [ Content-Type, { value: text/html, as: equal } ] |
| 295 | + - [ Server, { value: ATS, as: contains } ] |
| 296 | + content: |
| 297 | + encoding: plain |
| 298 | + data: Error |
| 299 | + verify: { as: contains } |
| 300 | +``` |
| 301 | + |
| 302 | +Replay Yaml Notes: |
| 303 | +- Each transaction will have a client-request and a server-response which each generate the HTTP request and response, respectively. |
| 304 | +- Each transaction's client-request has a uuid header whose value uniquely identifies the transaction. |
| 305 | +- proxy-request and proxy-response nodes do not generate traffic but rather verify content. See their value: and as: content above. |
| 306 | +- The proxy-response status: node verifies the status. This is generally important for verifying a 200 or some other HTTP response from ATS. |
| 307 | + |
| 308 | +Best Practices: |
| 309 | +- **Class Organization**: Use classes to group related test scenarios |
| 310 | +- **Separation of Concerns**: Separate server setup, ATS setup, and test execution as separate functions in the class. |
| 311 | +- **Descriptive Names**: Use clear, descriptive names for test runs and processes. Pass a descriptive name to `tr.AddTestRun('<concise test run description>')` |
| 312 | +- **Documentation**: Include docstrings explaining test purpose and setup as well as type hints. |
| 313 | +- **Gold Files**: Use gold files for complex expected outputs in subdirectories. These can be slow, so prefer ContainsExpression/ExcludesExpressions. |
| 314 | +- **Debug Configuration**: Enable relevant debug tags for troubleshooting. |
| 315 | +- **Cache IO**: if testing the ATS cache, one transaction has to populate the cache. If using VerifierClient, put a `delay: 100ms` in the client-request for the following transaction to make a 100ms delay for the cache to finish IO. |
| 316 | + |
| 317 | + |
| 318 | +When generating autests, follow these patterns and adapt the template to the specific testing scenario while maintaining the declarative, configuration-driven approach that characterizes the autest framework. |
0 commit comments