locustio / locust

Write scalable load tests in plain Python 🚗💨
https://locust.cloud
MIT License
25.06k stars 3k forks source link

resp.failure() shouldnt immediately log a failed request, just mark it a such #1335

Closed cyberw closed 4 years ago

cyberw commented 4 years ago

Because resp.failure() immediately logs a failure trows an exception, it is kind of hard to work with.

edit: I was confused, this first thing is entirely possible today

For instance, if you want to tag the request as a failure and then break out of the task entirely, you cant really do that in a straight forward way.

I would like to be able to do this:

    with self.client.post("/", catch_response=True) as resp:
        if resp.status_code != something:
            resp.failure()
            return

or even this (particularly useful when you have wrapped the request method and it does some kind of error checking internally, which you may want to override sometimes):

    with self.client.post("/", catch_response=True) as resp:
        if resp.status_code != something:
            resp.failure()
        if known_error:
            resp.success()

Probably we should also change it from being a method call to being a property.

Because this is potentially a breaking change for some testplans I would like to do it for 1.0

heyman commented 4 years ago

Hmm, catch_response=True + resp.failure() doesn't raise an exception? Maybe I'm misunderstanding your post?

The following code works perfectly fine for me (with either HttpLocust or FastHttpLocust):

from locust import HttpLocust, task, constant
from locust.contrib.fasthttp import FastHttpLocust

class TestUser(HttpLocust):
    host = "http://127.0.0.1:8089"
    wait_time = constant(1)

    @task
    def test_task(self):
        with self.client.post("/non-existing", catch_response=True) as resp:
            resp.failure("Noooo, fail!")
            print("request failed")
            return

        print("end of task (should never run)")

Command line output:

~/projects/locust master ❯ locust -f examples/issue_1335.py -c 1 -r 1 -t 3 --headless
[2020-04-17 16:57:44,237] MacBook-Air.localdomain/INFO/locust.main: Run time limit set to 3 seconds
[2020-04-17 16:57:44,237] MacBook-Air.localdomain/INFO/locust.main: Starting Locust 1.0.0
[2020-04-17 16:57:44,238] MacBook-Air.localdomain/INFO/locust.runners: Hatching and swarming 1 users at the rate 1 users/s (0 users already running)...
[2020-04-17 16:57:44,238] MacBook-Air.localdomain/INFO/locust.runners: All locusts hatched: TestUser: 1 (0 already running)
 Name                                                          # reqs      # fails     Avg     Min     Max  |  Median   req/s failures/s
--------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------
 Aggregated                                                         0     0(0.00%)       0       0       0  |       0    0.00    0.00

request failed
request failed
 Name                                                          # reqs      # fails     Avg     Min     Max  |  Median   req/s failures/s
--------------------------------------------------------------------------------------------------------------------------------------------
 POST /non-existing                                                 2   2(100.00%)       5       4       6  |       5    0.00    0.00
--------------------------------------------------------------------------------------------------------------------------------------------
 Aggregated                                                         2   2(100.00%)       5       4       6  |       5    0.00    0.00

request failed
[2020-04-17 16:57:47,080] MacBook-Air.localdomain/INFO/locust.main: Time limit reached. Stopping Locust.
[2020-04-17 16:57:47,080] MacBook-Air.localdomain/INFO/locust.main: Shutting down (exit code 1), bye.
[2020-04-17 16:57:47,080] MacBook-Air.localdomain/INFO/locust.main: Cleaning up runner...
[2020-04-17 16:57:47,081] MacBook-Air.localdomain/INFO/locust.main: Running teardowns...
 Name                                                          # reqs      # fails     Avg     Min     Max  |  Median   req/s failures/s
--------------------------------------------------------------------------------------------------------------------------------------------
 POST /non-existing                                                 3   3(100.00%)       5       4       6  |       5    1.48    1.48
--------------------------------------------------------------------------------------------------------------------------------------------
 Aggregated                                                         3   3(100.00%)       5       4       6  |       5    1.48    1.48

Percentage of the requests completed within given times
 Type                 Name                                                           # reqs    50%    66%    75%    80%    90%    95%    98%    99%  99.9% 99.99%   100%
------------------------------------------------------------------------------------------------------------------------------------------------------
 POST                 /non-existing                                                       3      5      5      7      7      7      7      7      7      7      7      7
------------------------------------------------------------------------------------------------------------------------------------------------------
 None                 Aggregated                                                          3      5      5      7      7      7      7      7      7      7      7      7

Error report
 # occurrences      Error
--------------------------------------------------------------------------------------------------------------------------------------------
 3                  POST /non-existing: "CatchResponseError('Noooo, fail!')"
--------------------------------------------------------------------------------------------------------------------------------------------
cyberw commented 4 years ago

Oh, right, I'm totally misremembering. It is possible to do the first thing I posted, just not the second one. For example, doing this:

def t1(self):
        with self.client.get("/", catch_response=True) as resp:
            resp.failure("foo")
            resp.success()

... will log two requests, one failure, and one success.

cyberw commented 4 years ago

... and that is bad because it makes it hard to build a more advanced client/user (for example, one that does custom error checking, but allows the error to be overridden/removed in cases where an error is expected)

heyman commented 4 years ago

Ah, I see what you mean! Definitely agree. Only the last call to either success() or failure() should count.

I have a failing test for it now. I'll set up a PR.

Probably we should also change it from being a method call to being a property.

I don't think it they should be properties. I think it's useful to be able to pass a message to the failure() method.

heyman commented 4 years ago

PR submitted!