Merging upstream version 1.27.2.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
d3f72a1e51
commit
cc1aa7d50e
15 changed files with 248 additions and 118 deletions
19
.github/workflows/ci.yml
vendored
19
.github/workflows/ci.yml
vendored
|
@ -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 }}
|
||||
|
||||
|
|
41
.github/workflows/codeql.yml
vendored
41
.github/workflows/codeql.yml
vendored
|
@ -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 }}"
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -12,3 +12,6 @@
|
|||
.cache/
|
||||
.coverage
|
||||
.coverage.*
|
||||
|
||||
.venv/
|
||||
venv/
|
||||
|
|
24
README.md
24
README.md
|
@ -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:
|
||||
|
||||
|
|
49
changelog.md
49
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
|
||||
|
|
|
@ -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:
|
||||
-----------
|
||||
|
|
|
@ -1 +1 @@
|
|||
__version__ = '1.27.0'
|
||||
__version__ = '1.27.2'
|
||||
|
|
|
@ -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())
|
||||
))
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -14,3 +14,4 @@ pyperclip>=1.8.1
|
|||
importlib_resources>=5.0.0
|
||||
pyaes>=1.6.1
|
||||
sqlglot>=5.1.3
|
||||
setuptools
|
||||
|
|
7
setup.py
7
setup.py
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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`:
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue