]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
tests/http: add timeout to running curl in test cases
authorStefan Eissing <stefan@eissing.org>
Fri, 17 Mar 2023 08:30:02 +0000 (09:30 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Sun, 26 Mar 2023 15:41:46 +0000 (17:41 +0200)
- we had a CI case once where `curl` seemingly did not
  return and it was hard to guess what happened.
- make curl execution in test cases time out after 60 seconds

Closes #10783

tests/http/scorecard.py
tests/http/testenv/curl.py
tests/http/testenv/env.py
tests/http/testenv/httpd.py

index 21dafe18144d4260657fc404f5f955c4c85d38b6..d9f789c9faf430c78f1eb0817f4960ce1e099445 100644 (file)
@@ -374,6 +374,7 @@ class ScoreCard:
         rv = 0
         self.env = Env()
         self.env.setup()
+        self.env.test_timeout = None
         self.httpd = None
         self.nghttpx = None
         self.caddy = None
index ec832eca0d627a1abd9689a174ae656f7c4f752c..13c4f8465d5a41a63e657db8494d5f86a8d04758 100644 (file)
@@ -45,9 +45,11 @@ class ExecResult:
     def __init__(self, args: List[str], exit_code: int,
                  stdout: List[str], stderr: List[str],
                  duration: Optional[timedelta] = None,
-                 with_stats: bool = False):
+                 with_stats: bool = False,
+                 exception: Optional[str] = None):
         self._args = args
         self._exit_code = exit_code
+        self._exception = exception
         self._stdout = stdout
         self._stderr = stderr
         self._duration = duration if duration is not None else timedelta()
@@ -69,7 +71,8 @@ class ExecResult:
                 pass
 
     def __repr__(self):
-        return f"ExecResult[code={self.exit_code}, args={self._args}, stdout={self._stdout}, stderr={self._stderr}]"
+        return f"ExecResult[code={self.exit_code}, exception={self._exception}, "\
+               f"args={self._args}, stdout={self._stdout}, stderr={self._stderr}]"
 
     def _parse_stats(self):
         self._stats = []
@@ -78,7 +81,6 @@ class ExecResult:
                 self._stats.append(json.loads(l))
             except:
                 log.error(f'not a JSON stat: {l}')
-                log.error(f'stdout is: {"".join(self._stdout)}')
                 break
 
     @property
@@ -197,8 +199,10 @@ class CurlClient:
         'h3': '--http3-only',
     }
 
-    def __init__(self, env: Env, run_dir: Optional[str] = None):
+    def __init__(self, env: Env, run_dir: Optional[str] = None,
+                 timeout: Optional[float] = None):
         self.env = env
+        self._timeout = timeout if timeout else env.test_timeout
         self._curl = os.environ['CURL'] if 'CURL' in os.environ else env.curl
         self._run_dir = run_dir if run_dir else os.path.join(env.gen_dir, 'curl')
         self._stdoutfile = f'{self._run_dir}/curl.stdout'
@@ -320,14 +324,22 @@ class CurlClient:
         self._rmf(self._headerfile)
         self._rmf(self._tracefile)
         start = datetime.now()
-        with open(self._stdoutfile, 'w') as cout:
-            with open(self._stderrfile, 'w') as cerr:
-                p = subprocess.run(args, stderr=cerr, stdout=cout,
-                                   cwd=self._run_dir, shell=False,
-                                   input=intext.encode() if intext else None)
+        exception = None
+        try:
+            with open(self._stdoutfile, 'w') as cout:
+                with open(self._stderrfile, 'w') as cerr:
+                    p = subprocess.run(args, stderr=cerr, stdout=cout,
+                                       cwd=self._run_dir, shell=False,
+                                       input=intext.encode() if intext else None,
+                                       timeout=self._timeout)
+                    exitcode = p.returncode
+        except subprocess.TimeoutExpired as e:
+            log.warning(f'Timeout after {self._timeout}s: {args}')
+            exitcode = -1
+            exception = 'TimeoutExpired'
         coutput = open(self._stdoutfile).readlines()
         cerrput = open(self._stderrfile).readlines()
-        return ExecResult(args=args, exit_code=p.returncode,
+        return ExecResult(args=args, exit_code=exitcode, exception=exception,
                           stdout=coutput, stderr=cerrput,
                           duration=datetime.now() - start,
                           with_stats=with_stats)
index bf08104d316e42e54b734160bd412b9845a1d1fc..c34d79129da1bc0191db33e214fd270d160e8793 100644 (file)
@@ -281,6 +281,7 @@ class Env:
         self._verbose = pytestconfig.option.verbose \
             if pytestconfig is not None else 0
         self._ca = None
+        self._test_timeout = 60.0  # seconds
 
     def issue_certs(self):
         if self._ca is None:
@@ -305,6 +306,14 @@ class Env:
     def verbose(self) -> int:
         return self._verbose
 
+    @property
+    def test_timeout(self) -> Optional[float]:
+        return self._test_timeout
+
+    @test_timeout.setter
+    def test_timeout(self, val: Optional[float]):
+        self._test_timeout = val
+
     @property
     def gen_dir(self) -> str:
         return self.CONFIG.gen_dir
index 4c7874581edf24e0defc6cfb16e5ba484c05a3d1..5b20d31e2a457ccdf736a306e69bc1a9af3cf613 100644 (file)
@@ -171,7 +171,8 @@ class Httpd:
         return False
 
     def wait_live(self, timeout: timedelta):
-        curl = CurlClient(env=self.env, run_dir=self._tmp_dir)
+        curl = CurlClient(env=self.env, run_dir=self._tmp_dir,
+                          timeout=timeout.total_seconds())
         try_until = datetime.now() + timeout
         while datetime.now() < try_until:
             r = curl.http_get(url=f'http://{self.env.domain1}:{self.env.http_port}/')