This is an automated email from the ASF dual-hosted git repository.

kaxilnaik pushed a commit to branch v1-10-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v1-10-test by this push:
     new 301a0b8  Fix quarantined tests - TestCliWebServer (#9596)
301a0b8 is described below

commit 301a0b8d7196244956f53970991d91b780dfdf2e
Author: Kamil BreguĊ‚a <mik-...@users.noreply.github.com>
AuthorDate: Wed Jul 1 19:10:58 2020 +0200

    Fix quarantined tests - TestCliWebServer (#9596)
---
 airflow/bin/cli.py                      |  24 ++--
 requirements/requirements-python2.7.txt |   9 +-
 requirements/requirements-python3.5.txt |  10 +-
 requirements/requirements-python3.6.txt |  14 +--
 requirements/requirements-python3.7.txt |  12 +-
 requirements/requirements-python3.8.txt |   6 +-
 requirements/setup-2.7.md5              |   2 +-
 requirements/setup-3.5.md5              |   2 +-
 requirements/setup-3.6.md5              |   2 +-
 requirements/setup-3.7.md5              |   2 +-
 requirements/setup-3.8.md5              |   2 +-
 setup.py                                |   4 +-
 tests/cli/test_cli.py                   |   7 +-
 tests/insert_extras.py                  |   2 +-
 tests/test_core.py                      | 193 ++++++++++++++++++++------------
 15 files changed, 167 insertions(+), 124 deletions(-)

diff --git a/airflow/bin/cli.py b/airflow/bin/cli.py
index 8334af6..51cf49d 100644
--- a/airflow/bin/cli.py
+++ b/airflow/bin/cli.py
@@ -800,8 +800,8 @@ class GunicornMonitor(LoggingMixin):
     respectively. Gunicorn guarantees that on TTOU workers are terminated
     gracefully and that the oldest worker is terminated.
 
-    :param gunicorn_master_proc:  handle for the main Gunicorn process
-    :param num_workers_expected:  Number of workers to run the Gunicorn web 
server
+    :param gunicorn_master_pid: pid of the main Gunicorn process
+    :param num_workers_expected: Number of workers to run the Gunicorn web 
server
     :param master_timeout: Number of seconds the webserver waits before 
killing gunicorn master that
         doesn't respond
     :param worker_refresh_interval: Number of seconds to wait before 
refreshing a batch of workers.
@@ -813,7 +813,7 @@ class GunicornMonitor(LoggingMixin):
     """
     def __init__(
         self,
-        gunicorn_master_proc,
+        gunicorn_master_pid,
         num_workers_expected,
         master_timeout,
         worker_refresh_interval,
@@ -821,7 +821,7 @@ class GunicornMonitor(LoggingMixin):
         reload_on_plugin_change
     ):
         super(GunicornMonitor, self).__init__()
-        self.gunicorn_master_proc = gunicorn_master_proc
+        self.gunicorn_master_proc = psutil.Process(gunicorn_master_pid)
         self.num_workers_expected = num_workers_expected
         self.master_timeout = master_timeout
         self.worker_refresh_interval = worker_refresh_interval
@@ -936,14 +936,15 @@ class GunicornMonitor(LoggingMixin):
         """
         Starts monitoring the webserver.
         """
+        self.log.debug("Start monitoring gunicorn")
         try:  # pylint: disable=too-many-nested-blocks
             self._wait_until_true(
                 lambda: self.num_workers_expected == 
self._get_num_workers_running(),
                 timeout=self.master_timeout
             )
             while True:
-                if self.gunicorn_master_proc.poll() is not None:
-                    sys.exit(self.gunicorn_master_proc.returncode)
+                if not self.gunicorn_master_proc.is_running():
+                    sys.exit(1)
                 self._check_workers()
                 # Throttle loop
                 time.sleep(1)
@@ -1122,15 +1123,16 @@ def webserver(args):
 
         gunicorn_master_proc = None
 
-        def kill_proc(dummy_signum, dummy_frame):
+        def kill_proc(signum, _):
+            log.info("Received signal: %s. Closing gunicorn.", signum)
             gunicorn_master_proc.terminate()
             gunicorn_master_proc.wait()
             sys.exit(0)
 
-        def monitor_gunicorn(gunicorn_master_proc):
+        def monitor_gunicorn(gunicorn_master_pid):
             # These run forever until SIG{INT, TERM, KILL, ...} signal is sent
             GunicornMonitor(
-                gunicorn_master_proc=gunicorn_master_proc,
+                gunicorn_master_pid=gunicorn_master_pid,
                 num_workers_expected=num_workers,
                 master_timeout=conf.getint('webserver', 
'web_server_master_timeout'),
                 worker_refresh_interval=conf.getint('webserver', 
'worker_refresh_interval', fallback=30),
@@ -1167,7 +1169,7 @@ def webserver(args):
                         time.sleep(0.1)
 
                 gunicorn_master_proc = psutil.Process(gunicorn_master_proc_pid)
-                monitor_gunicorn(gunicorn_master_proc)
+                monitor_gunicorn(gunicorn_master_proc.pid)
 
             stdout.close()
             stderr.close()
@@ -1177,7 +1179,7 @@ def webserver(args):
             signal.signal(signal.SIGINT, kill_proc)
             signal.signal(signal.SIGTERM, kill_proc)
 
-            monitor_gunicorn(gunicorn_master_proc)
+            monitor_gunicorn(gunicorn_master_proc.pid)
 
 
 @cli_utils.action_logging
diff --git a/requirements/requirements-python2.7.txt 
b/requirements/requirements-python2.7.txt
index e1d8154..6973e5a 100644
--- a/requirements/requirements-python2.7.txt
+++ b/requirements/requirements-python2.7.txt
@@ -1,4 +1,4 @@
-# Editable install with no version control (apache-airflow==1.10.10)
+# Editable install with no version control (apache-airflow==1.10.11)
 Babel==2.8.0
 Flask-Admin==1.5.4
 Flask-AppBuilder==1.13.1
@@ -157,12 +157,12 @@ hmsclient==0.1.1
 httplib2==0.18.1
 humanize==0.5.1
 hvac==0.10.4
-identify==1.4.20
+identify==1.4.21
 idna==2.10
 ijson==2.6.1
 imagesize==1.2.0
 importlib-metadata==1.7.0
-importlib-resources==2.0.1
+importlib-resources==3.0.0
 inflection==0.3.1
 ipaddress==1.0.23
 ipdb==0.13.3
@@ -258,7 +258,7 @@ pytest-rerunfailures==9.0
 pytest-timeout==1.4.1
 pytest-xdist==1.32.0
 pytest==4.6.11
-python-daemon==2.1.2
+python-daemon==2.2.4
 python-dateutil==2.8.1
 python-editor==1.0.4
 python-http-client==3.2.7
@@ -310,7 +310,6 @@ sshpubkeys==3.1.0
 sshtunnel==0.1.5
 tabulate==0.8.7
 tenacity==4.12.0
-termcolor==1.1.0
 testpath==0.4.4
 text-unidecode==1.3
 textwrap3==0.9.2
diff --git a/requirements/requirements-python3.5.txt 
b/requirements/requirements-python3.5.txt
index 20c9b75..ac06c01 100644
--- a/requirements/requirements-python3.5.txt
+++ b/requirements/requirements-python3.5.txt
@@ -1,4 +1,4 @@
-# Editable install with no version control (apache-airflow==1.10.10)
+# Editable install with no version control (apache-airflow==1.10.11)
 Babel==2.8.0
 Flask-Admin==1.5.4
 Flask-AppBuilder==1.13.1
@@ -143,12 +143,11 @@ hmsclient==0.1.1
 httplib2==0.18.1
 humanize==0.5.1
 hvac==0.10.4
-identify==1.4.20
+identify==1.4.21
 idna==2.10
-ijson==2.6.1
 imagesize==1.2.0
 importlib-metadata==1.7.0
-importlib-resources==2.0.1
+importlib-resources==3.0.0
 inflection==0.5.0
 ipdb==0.13.3
 ipython-genutils==0.2.0
@@ -244,7 +243,7 @@ pytest-rerunfailures==9.0
 pytest-timeout==1.4.1
 pytest-xdist==1.32.0
 pytest==5.4.3
-python-daemon==2.1.2
+python-daemon==2.2.4
 python-dateutil==2.8.1
 python-editor==1.0.4
 python-http-client==3.2.7
@@ -297,7 +296,6 @@ sshpubkeys==3.1.0
 sshtunnel==0.1.5
 tabulate==0.8.7
 tenacity==4.12.0
-termcolor==1.1.0
 text-unidecode==1.3
 textwrap3==0.9.2
 thrift-sasl==0.4.2
diff --git a/requirements/requirements-python3.6.txt 
b/requirements/requirements-python3.6.txt
index 275cd7c..615b800 100644
--- a/requirements/requirements-python3.6.txt
+++ b/requirements/requirements-python3.6.txt
@@ -1,4 +1,4 @@
-# Editable install with no version control (apache-airflow==1.10.10)
+# Editable install with no version control (apache-airflow==1.10.11)
 Babel==2.8.0
 Flask-Admin==1.5.4
 Flask-AppBuilder==2.3.4
@@ -62,9 +62,9 @@ beautifulsoup4==4.7.1
 billiard==3.6.3.0
 black==19.10b0
 blinker==1.4
-boto3==1.14.13
+boto3==1.14.14
 boto==2.49.0
-botocore==1.17.13
+botocore==1.17.14
 cached-property==1.5.1
 cachetools==4.1.1
 cassandra-driver==3.20.2
@@ -145,12 +145,11 @@ hmsclient==0.1.1
 httplib2==0.18.1
 humanize==0.5.1
 hvac==0.10.4
-identify==1.4.20
+identify==1.4.21
 idna==2.10
-ijson==2.6.1
 imagesize==1.2.0
 importlib-metadata==1.7.0
-importlib-resources==2.0.1
+importlib-resources==3.0.0
 inflection==0.5.0
 ipdb==0.13.3
 ipython-genutils==0.2.0
@@ -246,7 +245,7 @@ pytest-rerunfailures==9.0
 pytest-timeout==1.4.1
 pytest-xdist==1.32.0
 pytest==5.4.3
-python-daemon==2.1.2
+python-daemon==2.2.4
 python-dateutil==2.8.1
 python-editor==1.0.4
 python-http-client==3.2.7
@@ -301,7 +300,6 @@ sshpubkeys==3.1.0
 sshtunnel==0.1.5
 tabulate==0.8.7
 tenacity==4.12.0
-termcolor==1.1.0
 text-unidecode==1.3
 textwrap3==0.9.2
 thrift-sasl==0.4.2
diff --git a/requirements/requirements-python3.7.txt 
b/requirements/requirements-python3.7.txt
index 91eb574..7e9d467 100644
--- a/requirements/requirements-python3.7.txt
+++ b/requirements/requirements-python3.7.txt
@@ -1,4 +1,4 @@
-# Editable install with no version control (apache-airflow==1.10.10)
+# Editable install with no version control (apache-airflow==1.10.11)
 Babel==2.8.0
 Flask-Admin==1.5.4
 Flask-AppBuilder==2.3.4
@@ -62,9 +62,9 @@ beautifulsoup4==4.7.1
 billiard==3.6.3.0
 black==19.10b0
 blinker==1.4
-boto3==1.14.13
+boto3==1.14.14
 boto==2.49.0
-botocore==1.17.13
+botocore==1.17.14
 cached-property==1.5.1
 cachetools==4.1.1
 cassandra-driver==3.20.2
@@ -145,9 +145,8 @@ hmsclient==0.1.1
 httplib2==0.18.1
 humanize==0.5.1
 hvac==0.10.4
-identify==1.4.20
+identify==1.4.21
 idna==2.10
-ijson==2.6.1
 imagesize==1.2.0
 importlib-metadata==1.7.0
 inflection==0.5.0
@@ -245,7 +244,7 @@ pytest-rerunfailures==9.0
 pytest-timeout==1.4.1
 pytest-xdist==1.32.0
 pytest==5.4.3
-python-daemon==2.1.2
+python-daemon==2.2.4
 python-dateutil==2.8.1
 python-editor==1.0.4
 python-http-client==3.2.7
@@ -300,7 +299,6 @@ sshpubkeys==3.1.0
 sshtunnel==0.1.5
 tabulate==0.8.7
 tenacity==4.12.0
-termcolor==1.1.0
 text-unidecode==1.3
 textwrap3==0.9.2
 thrift-sasl==0.4.2
diff --git a/requirements/requirements-python3.8.txt 
b/requirements/requirements-python3.8.txt
index 56f161d..747ae42 100644
--- a/requirements/requirements-python3.8.txt
+++ b/requirements/requirements-python3.8.txt
@@ -1,4 +1,4 @@
-# Editable install with no version control (apache-airflow==1.10.10)
+# Editable install with no version control (apache-airflow==1.10.11)
 Babel==2.8.0
 Flask-Admin==1.5.4
 Flask-AppBuilder==2.3.4
@@ -145,7 +145,7 @@ hmsclient==0.1.1
 httplib2==0.18.1
 humanize==0.5.1
 hvac==0.10.4
-identify==1.4.20
+identify==1.4.21
 idna==2.10
 imagesize==1.2.0
 importlib-metadata==1.7.0
@@ -243,7 +243,7 @@ pytest-rerunfailures==9.0
 pytest-timeout==1.4.1
 pytest-xdist==1.32.0
 pytest==5.4.3
-python-daemon==2.1.2
+python-daemon==2.2.4
 python-dateutil==2.8.1
 python-editor==1.0.4
 python-http-client==3.2.7
diff --git a/requirements/setup-2.7.md5 b/requirements/setup-2.7.md5
index 7cb2b1c..7302c51 100644
--- a/requirements/setup-2.7.md5
+++ b/requirements/setup-2.7.md5
@@ -1 +1 @@
-9688bea64971365f2ab7771658b1fc80  /opt/airflow/setup.py
+da591fb5f6ed08129068e227610706cb  /opt/airflow/setup.py
diff --git a/requirements/setup-3.5.md5 b/requirements/setup-3.5.md5
index 7cb2b1c..7302c51 100644
--- a/requirements/setup-3.5.md5
+++ b/requirements/setup-3.5.md5
@@ -1 +1 @@
-9688bea64971365f2ab7771658b1fc80  /opt/airflow/setup.py
+da591fb5f6ed08129068e227610706cb  /opt/airflow/setup.py
diff --git a/requirements/setup-3.6.md5 b/requirements/setup-3.6.md5
index 7cb2b1c..7302c51 100644
--- a/requirements/setup-3.6.md5
+++ b/requirements/setup-3.6.md5
@@ -1 +1 @@
-9688bea64971365f2ab7771658b1fc80  /opt/airflow/setup.py
+da591fb5f6ed08129068e227610706cb  /opt/airflow/setup.py
diff --git a/requirements/setup-3.7.md5 b/requirements/setup-3.7.md5
index 7cb2b1c..7302c51 100644
--- a/requirements/setup-3.7.md5
+++ b/requirements/setup-3.7.md5
@@ -1 +1 @@
-9688bea64971365f2ab7771658b1fc80  /opt/airflow/setup.py
+da591fb5f6ed08129068e227610706cb  /opt/airflow/setup.py
diff --git a/requirements/setup-3.8.md5 b/requirements/setup-3.8.md5
index 7cb2b1c..7302c51 100644
--- a/requirements/setup-3.8.md5
+++ b/requirements/setup-3.8.md5
@@ -1 +1 @@
-9688bea64971365f2ab7771658b1fc80  /opt/airflow/setup.py
+da591fb5f6ed08129068e227610706cb  /opt/airflow/setup.py
diff --git a/setup.py b/setup.py
index fc14c3c..906c705 100644
--- a/setup.py
+++ b/setup.py
@@ -189,7 +189,7 @@ azure_blob_storage = [
     'azure-storage-blob<12.0',
 ]
 azure_container_instances = [
-    'azure-mgmt-containerinstance>=1.5.0'
+    'azure-mgmt-containerinstance>=1.5.0,<2'
 ]
 azure_cosmos = [
     'azure-cosmos>=3.0.1,<4',
@@ -580,7 +580,7 @@ INSTALL_REQUIREMENTS = [
     'pendulum==1.4.4',
     'psutil>=4.2.0, <6.0.0',
     'pygments>=2.0.1, <3.0',
-    'python-daemon>=2.1.1, <2.2',
+    'python-daemon>=2.1.1',
     'python-dateutil>=2.3, <3',
     'python-nvd3~=0.15.0',
     'python-slugify>=3.0.0,<5.0',
diff --git a/tests/cli/test_cli.py b/tests/cli/test_cli.py
index 5748057..d3e9fe2 100644
--- a/tests/cli/test_cli.py
+++ b/tests/cli/test_cli.py
@@ -821,9 +821,8 @@ class TestShowInfo(unittest.TestCase):
 class TestGunicornMonitor(unittest.TestCase):
 
     def setUp(self):
-        self.gunicorn_master_proc = mock.Mock(pid=2137)
         self.monitor = cli.GunicornMonitor(
-            gunicorn_master_proc=self.gunicorn_master_proc,
+            gunicorn_master_pid=1,
             num_workers_expected=4,
             master_timeout=60,
             worker_refresh_interval=60,
@@ -922,7 +921,7 @@ class 
TestGunicornMonitorGeneratePluginState(unittest.TestCase):
             self._prepare_test_file("{}/file3.txt".format(tempdir), 300)
 
             monitor = cli.GunicornMonitor(
-                gunicorn_master_proc=mock.MagicMock(),
+                gunicorn_master_pid=1,
                 num_workers_expected=4,
                 master_timeout=60,
                 worker_refresh_interval=60,
@@ -974,7 +973,7 @@ class TestCLIGetNumReadyWorkersRunning(unittest.TestCase):
         self.child = mock.MagicMock()
         self.process = mock.MagicMock()
         self.monitor = cli.GunicornMonitor(
-            gunicorn_master_proc=self.gunicorn_master_proc,
+            gunicorn_master_pid=1,
             num_workers_expected=4,
             master_timeout=60,
             worker_refresh_interval=60,
diff --git a/tests/insert_extras.py b/tests/insert_extras.py
index 871f24e..b38b543 100644
--- a/tests/insert_extras.py
+++ b/tests/insert_extras.py
@@ -58,7 +58,7 @@ def insert_documentation(file_path, content, header, footer):
 if __name__ == '__main__':
     install_file_path = os.path.join(AIRFLOW_SOURCES_DIR, 'INSTALL')
     contributing_file_path = os.path.join(AIRFLOW_SOURCES_DIR, 
'CONTRIBUTING.rst')
-    extras = wrap(", ".join(EXTRAS_REQUIREMENTS.keys()), 100)
+    extras = wrap(", ".join(sorted(EXTRAS_REQUIREMENTS.keys())) , 100)
     extras = [line + "\n" for line in extras]
     insert_documentation(install_file_path, extras, INSTALL_HEADER, 
INSTALL_FOOTER)
     insert_documentation(contributing_file_path, extras, RST_HEADER, 
RST_FOOTER)
diff --git a/tests/test_core.py b/tests/test_core.py
index 8e55aa5..5530f2a 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -27,7 +27,6 @@ import os
 import re
 import signal
 
-import pytest
 import sqlalchemy
 import subprocess
 import tempfile
@@ -38,7 +37,7 @@ from email.mime.multipart import MIMEMultipart
 from email.mime.text import MIMEText
 from numpy.testing import assert_array_almost_equal
 from six.moves.urllib.parse import urlencode
-from time import sleep
+from time import sleep, time
 
 from bs4 import BeautifulSoup
 
@@ -56,11 +55,13 @@ from airflow.operators.dummy_operator import DummyOperator
 from airflow.hooks.base_hook import BaseHook
 from airflow.hooks.sqlite_hook import SqliteHook
 from airflow.bin import cli
+from airflow.utils.file import TemporaryDirectory
 from airflow.www import app as application
 from airflow.settings import Session
 from airflow.utils import timezone
 from airflow.utils.timezone import datetime
 from airflow.utils.state import State
+from airflow.utils.timeout import timeout
 from airflow.utils.dates import days_ago, infer_time_unit, round_time, 
scale_time_units
 from airflow.exceptions import AirflowException
 from airflow.configuration import (
@@ -1649,23 +1650,35 @@ class CliTests(unittest.TestCase):
         os.remove('variables2.json')
         os.remove('variables3.json')
 
-    def _wait_pidfile(self, pidfile):
-        while True:
-            try:
-                with open(pidfile) as f:
-                    return int(f.read())
-            except Exception:
-                sleep(1)
+
+class TestCliWebServer(unittest.TestCase):
+    @classmethod
+    def setUpClass(cls):
+        cls.parser = cli.get_parser()
+
+    def setUp(self):
+        self._check_processes()
+        self._clean_pidfiles()
+
+    def tearDown(self):
+        self._check_processes()
+        self._clean_pidfiles()
 
     def _check_processes(self):
-        try:
-            # Confirm that webserver hasn't been launched.
-            # pgrep returns exit status 1 if no process matched.
-            self.assertEqual(1, subprocess.Popen(["pgrep", "-f", "-c", 
"airflow webserver"]).wait())
-            self.assertEqual(1, subprocess.Popen(["pgrep", "-c", 
"gunicorn"]).wait())
-        except:  # noqa: E722
+        # Confirm that webserver hasn't been launched.
+        # pgrep returns exit status 1 if no process matched.
+        exit_code_pgrep_webserver = subprocess.Popen(["pgrep", "-c", "-f", 
"airflow webserver"]).wait()
+        exit_code_pgrep_gunicorn = subprocess.Popen(["pgrep", "-c", "-f", 
"gunicorn"]).wait()
+        if exit_code_pgrep_webserver != 1 or exit_code_pgrep_gunicorn != 1:
             subprocess.Popen(["ps", "-ax"]).wait()
-            raise
+            if exit_code_pgrep_webserver != 1:
+                subprocess.Popen(["pkill", "-9", "-f", "airflow 
webserver"]).wait()
+            if exit_code_pgrep_gunicorn != 1:
+                subprocess.Popen(["pkill", "-9", "-f", "gunicorn"]).wait()
+
+            raise AssertionError(
+                "Background processes are running that prevent the test from 
passing successfully."
+            )
 
     def _clean_pidfiles(self):
         pidfile_webserver = cli.setup_locations("webserver")[0]
@@ -1675,70 +1688,106 @@ class CliTests(unittest.TestCase):
         if os.path.exists(pidfile_monitor):
             os.remove(pidfile_monitor)
 
-    @pytest.mark.quarantined
-    def test_cli_webserver_foreground(self):
-        self._check_processes()
-        self._clean_pidfiles()
-        try:
-
-            # Confirm that webserver hasn't been launched.
-            # pgrep returns exit status 1 if no process matched.
-            self.assertEqual(1, subprocess.Popen(["pgrep", "-c", 
"airflow"]).wait())
-            self.assertEqual(1, subprocess.Popen(["pgrep", "-c", 
"gunicorn"]).wait())
+    def _wait_pidfile(self, pidfile):
+        start_time = time()
+        while True:
+            try:
+                with open(pidfile) as file:
+                    return int(file.read())
+            except Exception:  # pylint: disable=broad-except
+                if start_time - time() > 60:
+                    raise
+                sleep(1)
 
+    def test_cli_webserver_foreground(self):
+        with mock.patch.dict(
+            "os.environ",
+            AIRFLOW__CORE__DAGS_FOLDER="/dev/null",
+            AIRFLOW__CORE__LOAD_EXAMPLES="False",
+            AIRFLOW__WEBSERVER__WORKERS="1"
+        ):
             # Run webserver in foreground and terminate it.
-            p = subprocess.Popen(["airflow", "webserver"])
-            p.terminate()
-            p.wait()
-
-            # Assert that no process remains.
-            self.assertEqual(1, subprocess.Popen(["pgrep", "-c", 
"airflow"]).wait())
-            self.assertEqual(1, subprocess.Popen(["pgrep", "-c", 
"gunicorn"]).wait())
-        finally:
-            self._check_processes()
-
-    @unittest.skipIf("TRAVIS" in os.environ and bool(os.environ["TRAVIS"]),
-                     "Skipping test due to lack of required file permission")
-    @pytest.mark.quarantined
-    def test_cli_webserver_foreground_with_pid(self):
-        # Run webserver in foreground with --pid option
-        pidfile = tempfile.mkstemp()[1]
-        p = subprocess.Popen(["airflow", "webserver", "--pid", pidfile])
+            proc = subprocess.Popen(["airflow", "webserver"])
+            self.assertEqual(None, proc.poll())
 
-        # Check the file specified by --pid option exists
-        self._wait_pidfile(pidfile)
+        # Wait for process
+        sleep(10)
 
         # Terminate webserver
-        p.terminate()
-        p.wait()
+        proc.terminate()
+        with timeout(60):
+            # -15 - the server was stopped before it started
+            #   0 - the server terminated correctly
+            self.assertIn(proc.wait(), (-15, 0))
+
+    def test_cli_webserver_foreground_with_pid(self):
+        with TemporaryDirectory(prefix='tmp-pid') as tmpdir:
+            pidfile = "{}/pidfile".format(tmpdir)
+            with mock.patch.dict(
+                "os.environ",
+                AIRFLOW__CORE__DAGS_FOLDER="/dev/null",
+                AIRFLOW__CORE__LOAD_EXAMPLES="False",
+                AIRFLOW__WEBSERVER__WORKERS="1"
+            ):
+                proc = subprocess.Popen(["airflow", "webserver", "--pid", 
pidfile])
+                self.assertEqual(None, proc.poll())
+
+            # Check the file specified by --pid option exists
+            self._wait_pidfile(pidfile)
+
+            # Terminate webserver
+            proc.terminate()
+            with timeout(60):
+                self.assertEqual(0, proc.wait())
 
-    @unittest.skipIf("TRAVIS" in os.environ and bool(os.environ["TRAVIS"]),
-                     "Skipping test due to lack of required file permission")
-    @pytest.mark.quarantined
     def test_cli_webserver_background(self):
         import psutil
-        self._check_processes()
-        self._clean_pidfiles()
-        try:
-            pidfile_webserver = cli.setup_locations("webserver")[0]
-            pidfile_monitor = cli.setup_locations("webserver-monitor")[0]
-
-            # Run webserver as daemon in background. Note that the wait method 
is not called.
-            subprocess.Popen(["airflow", "webserver", "-D"])
-
-            pid_monitor = self._wait_pidfile(pidfile_monitor)
-            self._wait_pidfile(pidfile_webserver)
-
-            # Assert that gunicorn and its monitor are launched.
-            self.assertEqual(0, subprocess.Popen(["pgrep", "-f", "-c", 
"airflow webserver"]).wait())
-            self.assertEqual(0, subprocess.Popen(["pgrep", "-c", 
"gunicorn"]).wait())
-
-            # Terminate monitor process.
-            proc = psutil.Process(pid_monitor)
-            proc.terminate()
-            proc.wait()
-        finally:
-            self._check_processes()
+        with TemporaryDirectory(prefix="gunicorn") as tmpdir, \
+            mock.patch.dict(
+                "os.environ",
+                AIRFLOW__CORE__DAGS_FOLDER="/dev/null",
+                AIRFLOW__CORE__LOAD_EXAMPLES="False",
+                AIRFLOW__WEBSERVER__WORKERS="1"):
+            pidfile_webserver = "{}/pidflow-webserver.pid".format(tmpdir)
+            pidfile_monitor = "{}/pidflow-webserver-monitor.pid".format(tmpdir)
+            stdout = "{}/airflow-webserver.out".format(tmpdir)
+            stderr = "{}/airflow-webserver.err".format(tmpdir)
+            logfile = "{}/airflow-webserver.log".format(tmpdir)
+            try:
+                # Run webserver as daemon in background. Note that the wait 
method is not called.
+                proc = subprocess.Popen([
+                    "airflow",
+                    "webserver",
+                    "--daemon",
+                    "--pid", pidfile_webserver,
+                    "--stdout", stdout,
+                    "--stderr", stderr,
+                    "--log-file", logfile,
+                ])
+                self.assertEqual(None, proc.poll())
+
+                pid_monitor = self._wait_pidfile(pidfile_monitor)
+                self._wait_pidfile(pidfile_webserver)
+
+                # Assert that gunicorn and its monitor are launched.
+                self.assertEqual(
+                    0, subprocess.Popen(["pgrep", "-f", "-c", "airflow 
webserver --daemon"]).wait()
+                )
+                self.assertEqual(0, subprocess.Popen(["pgrep", "-c", "-f", 
"gunicorn: master"]).wait())
+
+                # Terminate monitor process.
+                proc = psutil.Process(pid_monitor)
+                proc.terminate()
+                with timeout(120):
+                    self.assertIn(proc.wait(), (0, None))
+
+                self._check_processes()
+            except Exception:
+                # List all logs
+                subprocess.Popen(["ls", "-lah", tmpdir]).wait()
+                # Dump all logs
+                subprocess.Popen(["bash", "-c", "ls {}/* | xargs -n 1 -t 
cat".format(tmpdir)]).wait()
+                raise
 
     # Patch for causing webserver timeout
     @mock.patch("airflow.bin.cli.GunicornMonitor._get_num_workers_running", 
return_value=0)

Reply via email to