diff --git a/.github/tests-browser.yml b/.github/tests-browser.yml new file mode 100644 index 0000000000..04228b64ac --- /dev/null +++ b/.github/tests-browser.yml @@ -0,0 +1,16 @@ +name: test + +channels: + - conda-forge + - nodefaults + +dependencies: + # browser deps + - firefox >=78,<79 + - geckodriver + - nodejs >=12,<13 + - pandoc + # rest of python deps + - pip + - setuptools + - wheel diff --git a/.github/workflows/tests-browser.yml b/.github/workflows/tests-browser.yml new file mode 100644 index 0000000000..0ef37c5fcc --- /dev/null +++ b/.github/workflows/tests-browser.yml @@ -0,0 +1,78 @@ +name: Browser Tests + +on: + push: + branches: [master] + pull_request: + branches: [master] + +env: + JUPYTER_TEST_BROWSER: firefox + MOZ_HEADLESS: 1 + +jobs: + build: + runs-on: ${{ matrix.os }}-latest + strategy: + fail-fast: false + matrix: + os: [ubuntu, macos, windows] + python-version: ['3.6', '3.8', '3.9'] + defaults: + run: + shell: bash -l {0} + steps: + - name: Checkout + uses: actions/checkout@v1 + - name: Cache conda + uses: actions/cache@v1 + with: + path: ~/conda_pkgs_dir + key: + ${{ runner.os }}-conda-${{ matrix.python-version }}-${{ hashFiles('.github/tests-browser.yml') }} + restore-keys: | + ${{ runner.os }}-conda-${{ matrix.python-version }}- + - name: Install Browser Test Dependencies + uses: conda-incubator/setup-miniconda@v2 + with: + python-version: ${{ matrix.python-version }} + environment-file: .github/tests-browser.yml + - if: ${{ matrix.os == 'windows' }} + name: Install windows conda dependencies + run: conda install -c conda-forge -c msys2 -c nodefaults pywin32 + - name: List conda packages + run: | + conda list --explicit + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + - name: Cache pip + uses: actions/cache@v1 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('setup.py') }} + restore-keys: | + ${{ runner.os }}-pip-${{ matrix.python-version }}- + ${{ runner.os }}-pip- + - name: Build static assets + run: | + python setup.py build + - name: Install the Python dependencies + run: | + pip install -e .[test] codecov + - name: List installed packages + run: | + pip freeze + pip check + - name: Run selenium tests + timeout-minutes: 30 + run: | + py.test -sv notebook/tests/selenium ${{ matrix.selenium-pytest-args }} + - name: Install npm dependencies + run: | + npm install -g casperjs@1.1.3 phantomjs-prebuilt@2.1.7 + - name: Run legacy js tests + timeout-minutes: 30 + run: | + python -m notebook.jstest diff --git a/.github/workflows/tests-python.yml b/.github/workflows/tests-python.yml new file mode 100644 index 0000000000..39d1abfee6 --- /dev/null +++ b/.github/workflows/tests-python.yml @@ -0,0 +1,66 @@ +name: Python Tests + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + build: + runs-on: ${{ matrix.os }}-latest + strategy: + fail-fast: false + matrix: + os: [ubuntu, macos, windows] + python-version: ['3.6', '3.7', '3.8', '3.9', 'pypy3'] + exclude: + - os: windows + python-version: pypy3 + include: + # TODO: remove if fixed https://github.com/spyder-ide/pywinpty/issues/134 + - os: windows + python-version: '3.9' + pytest-args: --ignore notebook/terminal + - python-version: pypy3 + pytest-args: -k "not culling" + steps: + - name: Checkout + uses: actions/checkout@v1 + - name: Install Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + architecture: 'x64' + - name: Upgrade packaging dependencies + run: | + pip install --upgrade pip setuptools wheel --user + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + - name: Cache pip + uses: actions/cache@v1 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('setup.py') }} + restore-keys: | + ${{ runner.os }}-pip-${{ matrix.python-version }}- + ${{ runner.os }}-pip- + - uses: actions/setup-node@v1 + with: + node-version: '12' + - name: Build static assets + run: | + python setup.py build + - name: Install the Python dependencies + run: | + pip install -e .[test] codecov + - name: List installed packages + run: | + pip freeze + pip check + - name: Run the tests + timeout-minutes: 30 + run: | + pytest -vv notebook --cov notebook --ignore notebook/tests/selenium --cov-branch --cov-report term-missing:skip-covered --durations 10 ${{ matrix.pytest-args }} diff --git a/notebook/auth/login.py b/notebook/auth/login.py index 1ac434dc5e..db564aa533 100644 --- a/notebook/auth/login.py +++ b/notebook/auth/login.py @@ -73,13 +73,13 @@ def hashed_password(self): def passwd_check(self, a, b): return passwd_check(a, b) - + def post(self): typed_password = self.get_argument('password', default=u'') new_password = self.get_argument('new_password', default=u'') - + if self.get_login_available(self.settings): if self.passwd_check(self.hashed_password, typed_password) and not new_password: self.set_login_cookie(self, uuid.uuid4().hex) @@ -112,7 +112,7 @@ def set_login_cookie(cls, handler, user_id=None): handler.set_secure_cookie(handler.cookie_name, user_id, **cookie_options) return user_id - auth_header_pat = re.compile('token\s+(.+)', re.IGNORECASE) + auth_header_pat = re.compile(r'token\s+(.+)', re.IGNORECASE) @classmethod def get_token(cls, handler): @@ -197,7 +197,7 @@ def get_user(cls, handler): @classmethod def get_user_token(cls, handler): """Identify the user based on a token in the URL or Authorization header - + Returns: - uuid if authenticated - None if not diff --git a/notebook/jstest.py b/notebook/jstest.py index 2bb318af31..a7afc488c8 100644 --- a/notebook/jstest.py +++ b/notebook/jstest.py @@ -32,6 +32,12 @@ def popen_wait(p, timeout): return p.wait(timeout) NOTEBOOK_SHUTDOWN_TIMEOUT = 10 +SKIP_JS_GROUPS = [ + # doesn't appear to do anything + "mockextension", + # has its own test entrypoint + "selenium" +] have = {} have['casperjs'] = bool(which('casperjs')) @@ -60,7 +66,7 @@ def run(self): self.buffer.write(chunk) if self.echo: sys.stdout.write(bytes_to_str(chunk)) - + os.close(self.readfd) os.close(self.writefd) @@ -110,7 +116,7 @@ def __init__(self): def setup(self): """Create temporary directories etc. - + This is only called when we know the test group will be run. Things created here may be cleaned up by self.cleanup(). """ @@ -138,11 +144,11 @@ def wait(self): def print_extra_info(self): """Print extra information about this test run. - + If we're running in parallel and showing the concise view, this is only called if the test group fails. Otherwise, it's called before the test group is started. - + The base implementation does nothing, but it can be overridden by subclasses. """ @@ -189,11 +195,15 @@ def all_js_groups(): import glob test_dir = get_js_test_dir() all_subdirs = glob.glob(test_dir + '[!_]*/') - return [os.path.relpath(x, test_dir) for x in all_subdirs] + return [ + os.path.relpath(x, test_dir) + for x in all_subdirs + if os.path.relpath(x, test_dir) not in SKIP_JS_GROUPS + ] class JSController(TestController): """Run CasperJS tests """ - + requirements = ['casperjs'] def __init__(self, section, xunit=True, engine='phantomjs', url=None): @@ -206,11 +216,11 @@ def __init__(self, section, xunit=True, engine='phantomjs', url=None): # run with a base URL that would be escaped, # to test that we don't double-escape URLs self.base_url = '/a@b/' - self.slimer_failure = re.compile('^FAIL.*', flags=re.MULTILINE) + self.slimer_failure = re.compile(r'^FAIL.*', flags=re.MULTILINE) js_test_dir = get_js_test_dir() includes = '--includes=' + os.path.join(js_test_dir,'util.js') test_cases = os.path.join(js_test_dir, self.section) - self.cmd = ['casperjs', 'test', includes, test_cases, '--engine=%s' % self.engine] + self.cmd = [shutil.which('casperjs'), 'test', includes, test_cases, '--engine=%s' % self.engine] def setup(self): self.ipydir = TemporaryDirectory() @@ -317,7 +327,7 @@ def _init_server(self): 'nbserver-%i.json' % self.server.pid ) self._wait_for_server() - + def _wait_for_server(self): """Wait 30 seconds for the notebook server to start""" for i in range(300): @@ -336,14 +346,14 @@ def _wait_for_server(self): print("Notebook server-info file never arrived: %s" % self.server_info_file, file=sys.stderr ) - + def _failed_to_start(self): """Notebook server exited prematurely""" captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace') print("Notebook failed to start: ", file=sys.stderr) print(self.server_command) print(captured, file=sys.stderr) - + def _load_server_info(self): """Notebook server started, load connection info from JSON""" with open(self.server_info_file) as f: @@ -377,7 +387,7 @@ def cleanup(self): print("Notebook server still running (%s)" % self.server_info_file, file=sys.stderr ) - + self.stream_capturer.halt() TestController.cleanup(self) @@ -399,11 +409,11 @@ def prepare_controllers(options): def do_run(controller, buffer_output=True): """Setup and run a test controller. - + If buffer_output is True, no output is displayed, to avoid it appearing interleaved. In this case, the caller is responsible for displaying test output on failure. - + Returns ------- controller : TestController @@ -468,7 +478,7 @@ def _add(name, value): def run_jstestall(options): """Run the entire Javascript test suite. - + This function constructs TestControllers and runs them in subprocesses. Parameters diff --git a/notebook/templates/tree.html b/notebook/templates/tree.html index d71527fcbf..d31d66098a 100644 --- a/notebook/templates/tree.html +++ b/notebook/templates/tree.html @@ -44,17 +44,17 @@