diff options
-rw-r--r-- | .github/workflows/ci.yml | 19 | ||||
-rw-r--r-- | .github/workflows/codeql.yml | 41 | ||||
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | README.md | 24 | ||||
-rw-r--r-- | changelog.md | 49 | ||||
-rw-r--r-- | mycli/AUTHORS | 13 | ||||
-rw-r--r-- | mycli/__init__.py | 2 | ||||
-rw-r--r-- | mycli/clitoolbar.py | 5 | ||||
-rwxr-xr-x | mycli/main.py | 14 | ||||
-rw-r--r-- | mycli/sqlexecute.py | 43 | ||||
-rw-r--r-- | requirements-dev.txt | 1 | ||||
-rwxr-xr-x | setup.py | 7 | ||||
-rw-r--r-- | test/test_main.py | 49 | ||||
-rw-r--r-- | test/test_special_iocommands.py | 82 | ||||
-rw-r--r-- | test/test_sqlexecute.py | 14 |
15 files changed, 248 insertions, 118 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e91d4dd..fb34daa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,28 +10,31 @@ jobs: strategy: matrix: python-version: [ - '3.7', '3.8', '3.9', '3.10', + '3.11', + '3.12', ] include: - - python-version: '3.7' - os: ubuntu-18.04 # MySQL 5.7.32 - python-version: '3.8' - os: ubuntu-18.04 # MySQL 5.7.32 + os: ubuntu-20.04 # MySQL 8.0.36 - python-version: '3.9' - os: ubuntu-20.04 # MySQL 8.0.22 + os: ubuntu-20.04 # MySQL 8.0.36 - python-version: '3.10' - os: ubuntu-22.04 # MySQL 8.0.28 + os: ubuntu-22.04 # MySQL 8.0.36 + - python-version: '3.11' + os: ubuntu-22.04 # MySQL 8.0.36 + - python-version: '3.12' + os: ubuntu-22.04 # MySQL 8.0.36 runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index bd0617b..0000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: "CodeQL" - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - schedule: - - cron: "12 18 * * 1" - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ python ] - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - queries: +security-and-quality - - - name: Autobuild - uses: github/codeql-action/autobuild@v2 - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 - with: - category: "/language:${{ matrix.language }}" @@ -12,3 +12,6 @@ .cache/ .coverage .coverage.* + +.venv/ +venv/ @@ -1,8 +1,6 @@ # mycli [![Build Status](https://github.com/dbcli/mycli/workflows/mycli/badge.svg)](https://github.com/dbcli/mycli/actions?query=workflow%3Amycli) -[![PyPI](https://img.shields.io/pypi/v/mycli.svg)](https://pypi.python.org/pypi/mycli) -[![LGTM](https://img.shields.io/lgtm/grade/python/github/dbcli/mycli.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/dbcli/mycli/context:python) A command line client for MySQL that can do auto-completion and syntax highlighting. @@ -76,6 +74,9 @@ $ sudo apt-get install mycli # Only on debian or ubuntu --ssl-cert PATH X509 cert in PEM format. --ssl-key PATH X509 key in PEM format. --ssl-cipher TEXT SSL cipher to use. + --tls-version [TLSv1|TLSv1.1|TLSv1.2|TLSv1.3] + TLS protocol version for secure connection. + --ssl-verify-server-cert Verify server's "Common Name" in its cert against hostname used when connecting. This option is disabled by default. @@ -178,29 +179,10 @@ Fedora has a package available for mycli, install it using dnf: $ sudo dnf install mycli ``` -### RHEL, Centos - -I haven't built an RPM package for mycli for RHEL or Centos yet. So please use `pip` to install `mycli`. You can install pip on your system using: - -``` -$ sudo yum install python3-pip -``` - -Once that is installed, you can install mycli as follows: - -``` -$ sudo pip3 install mycli -``` - ### Windows Follow the instructions on this blogpost: https://www.codewall.co.uk/installing-using-mycli-on-windows/ -### Cygwin - -1. Make sure the following Cygwin packages are installed: -`python3`, `python3-pip`. -2. Install mycli: `pip3 install mycli` ### Thanks: diff --git a/changelog.md b/changelog.md index 122012d..fb32e5a 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,46 @@ +Upcoming Release (TBD) +====================== + +Bug Fixes: +---------- + + +Internal: +--------- + +Features: +--------- + + +1.27.2 (2024/04/03) +=================== + +Bug Fixes: +---------- + +* Don't use default prompt when one is not supplied to the --prompt option. + + +1.27.1 (2024/03/28) +=================== + + +Bug Fixes: +---------- + +* Don't install tests. +* Do not ignore the socket passed with the -S option, even when no port is passed +* Fix unexpected exception when using dsn without username & password (Thanks: [Will Wang]) +* Let the `--prompt` option act normally with its predefined default value + + + +Internal: +--------- +* paramiko is newer than 2.11.0 now, remove version pinning `cryptography`. +* Drop support for Python 3.7 + + 1.27.0 (2023/08/11) =================== @@ -14,6 +57,7 @@ Bug Fixes: * Remove vi-mode bindings for prettify/unprettify. * Honor `\G` when executing from commandline with `-e`. * Correctly report the version of TiDB. +* Revised `botton` spelling mistakes with `bottom` in `mycli/clitoolbar.py` 1.26.1 (2022/09/01) @@ -35,6 +79,10 @@ Features: * Add prettify/unprettify keybindings to format the current statement using `sqlglot`. +Features: +--------- +* Add `--tls-version` option to control the tls version used. + Internal: --------- * Pin `cryptography` to suppress `paramiko` warning, helping CI complete and presumably affecting some users. @@ -950,3 +998,4 @@ Bug Fixes: [William GARCIA]: https://github.com/willgarcia [xeron]: https://github.com/xeron [Zach DeCook]: https://zachdecook.com +[Will Wang]: https://github.com/willww64 diff --git a/mycli/AUTHORS b/mycli/AUTHORS index a805465..e9c73aa 100644 --- a/mycli/AUTHORS +++ b/mycli/AUTHORS @@ -1,11 +1,7 @@ -Project Lead: -------------- - * Thomas Roten - - Core Developers: ---------------- + * Thomas Roten * Irina Truong * Matheus Rosa * Darik Gamble @@ -35,6 +31,7 @@ Contributors: * Daniel Black * Daniel West * Daniël van Eeden + * Fabrizio Gennari * François Pietka * Frederic Aoustin * Georgy Frolov @@ -94,6 +91,12 @@ Contributors: * Arvind Mishra * Kevin Schmeichel * Mel Dafert + * Thomas Copper + * Will Wang + * Alfred Wingate + * Zhanze Wang + * Houston Wong + Created by: ----------- diff --git a/mycli/__init__.py b/mycli/__init__.py index e2ba8ba..b5476c1 100644 --- a/mycli/__init__.py +++ b/mycli/__init__.py @@ -1 +1 @@ -__version__ = '1.27.0' +__version__ = '1.27.2' diff --git a/mycli/clitoolbar.py b/mycli/clitoolbar.py index 24d1108..52b6ee4 100644 --- a/mycli/clitoolbar.py +++ b/mycli/clitoolbar.py @@ -7,8 +7,7 @@ from .packages import special def create_toolbar_tokens_func(mycli, show_fish_help): """Return a function that generates the toolbar tokens.""" def get_toolbar_tokens(): - result = [] - result.append(('class:bottom-toolbar', ' ')) + result = [('class:bottom-toolbar', ' ')] if mycli.multi_line: delimiter = special.get_current_delimiter() @@ -26,7 +25,7 @@ def create_toolbar_tokens_func(mycli, show_fish_help): '[F3] Multiline: OFF ')) if mycli.prompt_app.editing_mode == EditingMode.VI: result.append(( - 'class:botton-toolbar.on', + 'class:bottom-toolbar.on', 'Vi-mode ({})'.format(_get_vi_mode()) )) diff --git a/mycli/main.py b/mycli/main.py index 02a09cf..ce4dff7 100755 --- a/mycli/main.py +++ b/mycli/main.py @@ -93,6 +93,7 @@ SUPPORT_INFO = ( class MyCli(object): default_prompt = '\\t \\u@\\h:\\d> ' + default_prompt_splitln = '\\u@\\h\\n(\\t):\\d>' max_len_prompt = 45 defaults_suffix = None @@ -427,6 +428,7 @@ class MyCli(object): port = 3306 if not host or host == 'localhost': socket = ( + socket or cnf['socket'] or cnf['default_socket'] or guess_socket_location() @@ -643,7 +645,7 @@ class MyCli(object): def get_message(): prompt = self.get_prompt(self.prompt_format) if self.prompt_format == self.default_prompt and len(prompt) > self.max_len_prompt: - prompt = self.get_prompt('\\d> ') + prompt = self.get_prompt(self.default_prompt_splitln) prompt = prompt.replace("\\x1b", "\x1b") return ANSI(prompt) @@ -1135,6 +1137,9 @@ class MyCli(object): @click.option('--ssl-key', help='X509 key in PEM format.', type=click.Path(exists=True)) @click.option('--ssl-cipher', help='SSL cipher to use.') +@click.option('--tls-version', + type=click.Choice(['TLSv1', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3'], case_sensitive=False), + help='TLS protocol version for secure connection.') @click.option('--ssl-verify-server-cert', is_flag=True, help=('Verify server\'s "Common Name" in its cert against ' 'hostname used when connecting. This option is disabled ' @@ -1186,8 +1191,8 @@ def cli(database, user, host, port, socket, password, dbname, version, verbose, prompt, logfile, defaults_group_suffix, defaults_file, login_path, auto_vertical_output, local_infile, ssl_enable, ssl_ca, ssl_capath, ssl_cert, ssl_key, ssl_cipher, - ssl_verify_server_cert, table, csv, warn, execute, myclirc, dsn, - list_dsn, ssh_user, ssh_host, ssh_port, ssh_password, + tls_version, ssl_verify_server_cert, table, csv, warn, execute, + myclirc, dsn, list_dsn, ssh_user, ssh_host, ssh_port, ssh_password, ssh_key_filename, list_ssh_config, ssh_config_path, ssh_config_host, init_command, charset, password_file): """A MySQL terminal client with auto-completion and syntax highlighting. @@ -1246,6 +1251,7 @@ def cli(database, user, host, port, socket, password, dbname, 'key': ssl_key and os.path.expanduser(ssl_key), 'capath': ssl_capath, 'cipher': ssl_cipher, + 'tls_version': tls_version, 'check_hostname': ssl_verify_server_cert, } @@ -1278,7 +1284,7 @@ def cli(database, user, host, port, socket, password, dbname, uri = urlparse(dsn_uri) if not database: database = uri.path[1:] # ignore the leading fwd slash - if not user: + if not user and uri.username is not None: user = unquote(uri.username) if not password and uri.password is not None: password = unquote(uri.password) diff --git a/mycli/sqlexecute.py b/mycli/sqlexecute.py index fecbefb..bd5f5d9 100644 --- a/mycli/sqlexecute.py +++ b/mycli/sqlexecute.py @@ -176,11 +176,15 @@ class SQLExecute(object): if init_command and len(list(special.split_queries(init_command))) > 1: client_flag |= pymysql.constants.CLIENT.MULTI_STATEMENTS + ssl_context = None + if ssl: + ssl_context = self._create_ssl_ctx(ssl) + conn = pymysql.connect( database=db, user=user, password=password, host=host, port=port, unix_socket=socket, use_unicode=True, charset=charset, autocommit=True, client_flag=client_flag, - local_infile=local_infile, conv=conv, ssl=ssl, program_name="mycli", + local_infile=local_infile, conv=conv, ssl=ssl_context, program_name="mycli", defer_connect=defer_connect, init_command=init_command ) @@ -354,3 +358,40 @@ class SQLExecute(object): def change_db(self, db): self.conn.select_db(db) self.dbname = db + + def _create_ssl_ctx(self, sslp): + import ssl + + ca = sslp.get("ca") + capath = sslp.get("capath") + hasnoca = ca is None and capath is None + ctx = ssl.create_default_context(cafile=ca, capath=capath) + ctx.check_hostname = not hasnoca and sslp.get("check_hostname", True) + ctx.verify_mode = ssl.CERT_NONE if hasnoca else ssl.CERT_REQUIRED + if "cert" in sslp: + ctx.load_cert_chain(sslp["cert"], keyfile=sslp.get("key")) + if "cipher" in sslp: + ctx.set_ciphers(sslp["cipher"]) + + # raise this default to v1.1 or v1.2? + ctx.minimum_version = ssl.TLSVersion.TLSv1 + + if "tls_version" in sslp: + tls_version = sslp["tls_version"] + + if tls_version == "TLSv1": + ctx.minimum_version = ssl.TLSVersion.TLSv1 + ctx.maximum_version = ssl.TLSVersion.TLSv1 + elif tls_version == "TLSv1.1": + ctx.minimum_version = ssl.TLSVersion.TLSv1_1 + ctx.maximum_version = ssl.TLSVersion.TLSv1_1 + elif tls_version == "TLSv1.2": + ctx.minimum_version = ssl.TLSVersion.TLSv1_2 + ctx.maximum_version = ssl.TLSVersion.TLSv1_2 + elif tls_version == "TLSv1.3": + ctx.minimum_version = ssl.TLSVersion.TLSv1_3 + ctx.maximum_version = ssl.TLSVersion.TLSv1_3 + else: + _logger.error('Invalid tls version: %s', tls_version) + + return ctx diff --git a/requirements-dev.txt b/requirements-dev.txt index c2cd04b..3f5fbdf 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -14,3 +14,4 @@ pyperclip>=1.8.1 importlib_resources>=5.0.0 pyaes>=1.6.1 sqlglot>=5.1.3 +setuptools @@ -18,9 +18,8 @@ description = 'CLI for MySQL Database. With auto-completion and syntax highlight install_requirements = [ 'click >= 7.0', - # Temporary to suppress paramiko Blowfish warning which breaks CI. - # Pinning cryptography should not be needed after paramiko 2.11.0. - 'cryptography == 36.0.2', + # Pinning cryptography is not needed after paramiko 2.11.0. Correct it + 'cryptography >= 1.0.0', # 'Pygments>=1.6,<=2.11.1', 'Pygments>=1.6', 'prompt_toolkit>=3.0.6,<4.0.0', @@ -95,7 +94,7 @@ setup( author_email='mycli-dev@googlegroups.com', version=version, url='http://mycli.net', - packages=find_packages(), + packages=find_packages(exclude=['test*']), package_data={'mycli': ['myclirc', 'AUTHORS', 'SPONSORS']}, description=description, long_description=description, diff --git a/test/test_main.py b/test/test_main.py index 64cba0a..589d6cd 100644 --- a/test/test_main.py +++ b/test/test_main.py @@ -254,23 +254,21 @@ def test_conditional_pager(monkeypatch): SPECIAL_COMMANDS['pager'].handler('') -def test_reserved_space_is_integer(): +def test_reserved_space_is_integer(monkeypatch): """Make sure that reserved space is returned as an integer.""" def stub_terminal_size(): return (5, 5) - old_func = shutil.get_terminal_size - - shutil.get_terminal_size = stub_terminal_size - mycli = MyCli() - assert isinstance(mycli.get_reserved_space(), int) - - shutil.get_terminal_size = old_func + with monkeypatch.context() as m: + m.setattr(shutil, 'get_terminal_size', stub_terminal_size) + mycli = MyCli() + assert isinstance(mycli.get_reserved_space(), int) def test_list_dsn(): runner = CliRunner() - with NamedTemporaryFile(mode="w") as myclirc: + # keep Windows from locking the file with delete=False + with NamedTemporaryFile(mode="w",delete=False) as myclirc: myclirc.write(dedent("""\ [alias_dsn] test = mysql://test/test @@ -281,6 +279,15 @@ def test_list_dsn(): assert result.output == "test\n" result = runner.invoke(cli, args=args + ['--verbose']) assert result.output == "test : mysql://test/test\n" + + # delete=False means we should try to clean up + try: + if os.path.exists(myclirc.name): + os.remove(myclirc.name) + except Exception as e: + print(f"An error occurred while attempting to delete the file: {e}") + + def test_prettify_statement(): @@ -299,7 +306,8 @@ def test_unprettify_statement(): def test_list_ssh_config(): runner = CliRunner() - with NamedTemporaryFile(mode="w") as ssh_config: + # keep Windows from locking the file with delete=False + with NamedTemporaryFile(mode="w",delete=False) as ssh_config: ssh_config.write(dedent("""\ Host test Hostname test.example.com @@ -313,6 +321,13 @@ def test_list_ssh_config(): assert "test\n" in result.output result = runner.invoke(cli, args=args + ['--verbose']) assert "test : test.example.com\n" in result.output + + # delete=False means we should try to clean up + try: + if os.path.exists(ssh_config.name): + os.remove(ssh_config.name) + except Exception as e: + print(f"An error occurred while attempting to delete the file: {e}") def test_dsn(monkeypatch): @@ -466,7 +481,8 @@ def test_ssh_config(monkeypatch): runner = CliRunner() # Setup temporary configuration - with NamedTemporaryFile(mode="w") as ssh_config: + # keep Windows from locking the file with delete=False + with NamedTemporaryFile(mode="w",delete=False) as ssh_config: ssh_config.write(dedent("""\ Host test Hostname test.example.com @@ -489,8 +505,8 @@ def test_ssh_config(monkeypatch): MockMyCli.connect_args["ssh_user"] == "joe" and \ MockMyCli.connect_args["ssh_host"] == "test.example.com" and \ MockMyCli.connect_args["ssh_port"] == 22222 and \ - MockMyCli.connect_args["ssh_key_filename"] == os.getenv( - "HOME") + "/.ssh/gateway" + MockMyCli.connect_args["ssh_key_filename"] == os.path.expanduser( + "~") + "/.ssh/gateway" # When a user supplies a ssh config host as argument to mycli, # and used command line arguments, use the command line @@ -512,6 +528,13 @@ def test_ssh_config(monkeypatch): MockMyCli.connect_args["ssh_host"] == "arg_host" and \ MockMyCli.connect_args["ssh_port"] == 3 and \ MockMyCli.connect_args["ssh_key_filename"] == "/path/to/key" + + # delete=False means we should try to clean up + try: + if os.path.exists(ssh_config.name): + os.remove(ssh_config.name) + except Exception as e: + print(f"An error occurred while attempting to delete the file: {e}") @dbtest diff --git a/test/test_special_iocommands.py b/test/test_special_iocommands.py index 8b6be33..d0ca45f 100644 --- a/test/test_special_iocommands.py +++ b/test/test_special_iocommands.py @@ -50,25 +50,49 @@ def test_editor_command(): os.environ['EDITOR'] = 'true' os.environ['VISUAL'] = 'true' - mycli.packages.special.open_external_editor(sql=r'select 1') == "select 1" + # Set the editor to Notepad on Windows + if os.name != 'nt': + mycli.packages.special.open_external_editor(sql=r'select 1') == "select 1" + else: + pytest.skip('Skipping on Windows platform.') + def test_tee_command(): mycli.packages.special.write_tee(u"hello world") # write without file set - with tempfile.NamedTemporaryFile() as f: + # keep Windows from locking the file with delete=False + with tempfile.NamedTemporaryFile(delete=False) as f: mycli.packages.special.execute(None, u"tee " + f.name) mycli.packages.special.write_tee(u"hello world") - assert f.read() == b"hello world\n" + if os.name=='nt': + assert f.read() == b"hello world\r\n" + else: + assert f.read() == b"hello world\n" mycli.packages.special.execute(None, u"tee -o " + f.name) mycli.packages.special.write_tee(u"hello world") f.seek(0) - assert f.read() == b"hello world\n" + if os.name=='nt': + assert f.read() == b"hello world\r\n" + else: + assert f.read() == b"hello world\n" mycli.packages.special.execute(None, u"notee") mycli.packages.special.write_tee(u"hello world") f.seek(0) - assert f.read() == b"hello world\n" + if os.name=='nt': + assert f.read() == b"hello world\r\n" + else: + assert f.read() == b"hello world\n" + + # remove temp file + # delete=False means we should try to clean up + try: + if os.path.exists(f.name): + os.remove(f.name) + except Exception as e: + print(f"An error occurred while attempting to delete the file: {e}") + def test_tee_command_error(): @@ -82,6 +106,8 @@ def test_tee_command_error(): @dbtest + +@pytest.mark.skipif(os.name == "nt", reason="Bug: fails on Windows, needs fixing, singleton of FQ not working right") def test_favorite_query(): with db_connection().cursor() as cur: query = u'select "✔"' @@ -98,16 +124,29 @@ def test_once_command(): mycli.packages.special.execute(None, u"\\once /proc/access-denied") mycli.packages.special.write_once(u"hello world") # write without file set - with tempfile.NamedTemporaryFile() as f: + # keep Windows from locking the file with delete=False + with tempfile.NamedTemporaryFile(delete=False) as f: mycli.packages.special.execute(None, u"\\once " + f.name) mycli.packages.special.write_once(u"hello world") - assert f.read() == b"hello world\n" + if os.name=='nt': + assert f.read() == b"hello world\r\n" + else: + assert f.read() == b"hello world\n" mycli.packages.special.execute(None, u"\\once -o " + f.name) mycli.packages.special.write_once(u"hello world line 1") mycli.packages.special.write_once(u"hello world line 2") f.seek(0) - assert f.read() == b"hello world line 1\nhello world line 2\n" + if os.name=='nt': + assert f.read() == b"hello world line 1\r\nhello world line 2\r\n" + else: + assert f.read() == b"hello world line 1\nhello world line 2\n" + # delete=False means we should try to clean up + try: + if os.path.exists(f.name): + os.remove(f.name) + except Exception as e: + print(f"An error occurred while attempting to delete the file: {e}") def test_pipe_once_command(): @@ -118,9 +157,14 @@ def test_pipe_once_command(): mycli.packages.special.execute( None, u"\\pipe_once /proc/access-denied") - mycli.packages.special.execute(None, u"\\pipe_once wc") - mycli.packages.special.write_once(u"hello world") - mycli.packages.special.unset_pipe_once_if_written() + if os.name == 'nt': + mycli.packages.special.execute(None, u'\\pipe_once python -c "import sys; print(len(sys.stdin.read().strip()))"') + mycli.packages.special.write_once(u"hello world") + mycli.packages.special.unset_pipe_once_if_written() + else: + mycli.packages.special.execute(None, u"\\pipe_once wc") + mycli.packages.special.write_once(u"hello world") + mycli.packages.special.unset_pipe_once_if_written() # how to assert on wc output? @@ -128,12 +172,21 @@ def test_parseargfile(): """Test that parseargfile expands the user directory.""" expected = {'file': os.path.join(os.path.expanduser('~'), 'filename'), 'mode': 'a'} - assert expected == mycli.packages.special.iocommands.parseargfile( - '~/filename') + + if os.name=='nt': + assert expected == mycli.packages.special.iocommands.parseargfile( + '~\\filename') + else: + assert expected == mycli.packages.special.iocommands.parseargfile( + '~/filename') expected = {'file': os.path.join(os.path.expanduser('~'), 'filename'), 'mode': 'w'} - assert expected == mycli.packages.special.iocommands.parseargfile( + if os.name=='nt': + assert expected == mycli.packages.special.iocommands.parseargfile( + '-o ~\\filename') + else: + assert expected == mycli.packages.special.iocommands.parseargfile( '-o ~/filename') @@ -162,6 +215,7 @@ def test_watch_query_iteration(): @dbtest +@pytest.mark.skipif(os.name == "nt", reason="Bug: Win handles this differently. May need to refactor watch_query to work for Win") def test_watch_query_full(): """Test that `watch_query`: diff --git a/test/test_sqlexecute.py b/test/test_sqlexecute.py index 163c850..ca186bc 100644 --- a/test/test_sqlexecute.py +++ b/test/test_sqlexecute.py @@ -117,6 +117,7 @@ def test_multiple_queries_same_line_syntaxerror(executor): @dbtest +@pytest.mark.skipif(os.name == "nt", reason="Bug: fails on Windows, needs fixing, singleton of FQ not working right") def test_favorite_query(executor): set_expanded_output(False) run(executor, "create table test(a text)") @@ -136,6 +137,7 @@ def test_favorite_query(executor): @dbtest +@pytest.mark.skipif(os.name == "nt", reason="Bug: fails on Windows, needs fixing, singleton of FQ not working right") def test_favorite_query_multiple_statement(executor): set_expanded_output(False) run(executor, "create table test(a text)") @@ -159,6 +161,7 @@ def test_favorite_query_multiple_statement(executor): @dbtest +@pytest.mark.skipif(os.name == "nt", reason="Bug: fails on Windows, needs fixing, singleton of FQ not working right") def test_favorite_query_expanded_output(executor): set_expanded_output(False) run(executor, '''create table test(a text)''') @@ -195,16 +198,21 @@ def test_cd_command_without_a_folder_name(executor): @dbtest def test_system_command_not_found(executor): results = run(executor, 'system xyz') - assert_result_equal(results, status='OSError: No such file or directory', - assert_contains=True) + if os.name=='nt': + assert_result_equal(results, status='OSError: The system cannot find the file specified', + assert_contains=True) + else: + assert_result_equal(results, status='OSError: No such file or directory', + assert_contains=True) @dbtest def test_system_command_output(executor): + eol = os.linesep test_dir = os.path.abspath(os.path.dirname(__file__)) test_file_path = os.path.join(test_dir, 'test.txt') results = run(executor, 'system cat {0}'.format(test_file_path)) - assert_result_equal(results, status='mycli rocks!\n') + assert_result_equal(results, status=f'mycli rocks!{eol}') @dbtest |