mirror of
https://github.com/jtesta/ssh-audit.git
synced 2024-11-16 13:35:39 +01:00
Merged all_my_patches branch to master, since a new project maintainer is needed.
This commit is contained in:
commit
b35ca6c6f3
37
.appveyor.yml
Normal file
37
.appveyor.yml
Normal file
@ -0,0 +1,37 @@
|
||||
version: '1.7.1.dev.{build}'
|
||||
|
||||
build: off
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- develop
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- PYTHON: "C:\\Python26"
|
||||
- PYTHON: "C:\\Python26-x64"
|
||||
- PYTHON: "C:\\Python27"
|
||||
- PYTHON: "C:\\Python27-x64"
|
||||
- PYTHON: "C:\\Python33"
|
||||
- PYTHON: "C:\\Python33-x64"
|
||||
- PYTHON: "C:\\Python34"
|
||||
- PYTHON: "C:\\Python34-x64"
|
||||
- PYTHON: "C:\\Python35"
|
||||
- PYTHON: "C:\\Python35-x64"
|
||||
- PYTHON: "C:\\Python36"
|
||||
- PYTHON: "C:\\Python36-x64"
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
cache:
|
||||
- '%LOCALAPPDATA%\pip\Cache'
|
||||
- .downloads -> .appveyor.yml
|
||||
|
||||
install:
|
||||
- "cmd /c .\\test\\tools\\ci-win.cmd install"
|
||||
|
||||
test_script:
|
||||
- "cmd /c .\\test\\tools\\ci-win.cmd test"
|
||||
|
||||
on_failure:
|
||||
- ps: get-content .tox\*\log\*
|
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,5 +1,8 @@
|
||||
*~
|
||||
*.pyc
|
||||
html/
|
||||
venv/
|
||||
venv*/
|
||||
.cache/
|
||||
.tox
|
||||
.coverage*
|
||||
reports/
|
||||
.scannerwork/
|
||||
|
94
.travis.yml
94
.travis.yml
@ -1,18 +1,80 @@
|
||||
language: python
|
||||
python:
|
||||
- 2.6
|
||||
- 2.7
|
||||
- 3.3
|
||||
- 3.4
|
||||
- 3.5
|
||||
- pypy
|
||||
- pypy3
|
||||
install:
|
||||
- pip install --upgrade pytest
|
||||
- pip install --upgrade pytest-cov
|
||||
- pip install --upgrade coveralls
|
||||
script:
|
||||
- py.test --cov-report= --cov=ssh-audit -v test
|
||||
after_success:
|
||||
- coveralls
|
||||
sudo: false
|
||||
matrix:
|
||||
include:
|
||||
# (default)
|
||||
- os: linux
|
||||
python: 2.6
|
||||
- os: linux
|
||||
python: 2.7
|
||||
env: SQ=1
|
||||
- os: linux
|
||||
python: 3.3
|
||||
- os: linux
|
||||
python: 3.4
|
||||
- os: linux
|
||||
python: 3.5
|
||||
- os: linux
|
||||
python: 3.6
|
||||
- os: linux
|
||||
python: pypy
|
||||
- os: linux
|
||||
python: pypy3
|
||||
- os: linux
|
||||
python: 3.7-dev
|
||||
# Ubuntu 12.04
|
||||
- os: linux
|
||||
dist: precise
|
||||
language: generic
|
||||
env: PY_VER=py26,py27,py33,py34,py35,py36,pypy,pypy3 PY_ORIGIN=pyenv
|
||||
# Ubuntu 14.04
|
||||
- os: linux
|
||||
dist: trusty
|
||||
language: generic
|
||||
env: PY_VER=py26,py27,py33,py34,py35,py36,pypy,pypy3 PY_ORIGIN=pyenv
|
||||
# macOS 10.12 Sierra
|
||||
- os: osx
|
||||
osx_image: xcode8.3
|
||||
language: generic
|
||||
env: PY_VER=py26,py27,py33,py34,py35,py36,pypy,pypy3
|
||||
# Mac OS X 10.11 El Capitan
|
||||
- os: osx
|
||||
osx_image: xcode7.3
|
||||
language: generic
|
||||
env: PY_VER=py26,py27,py33,py34,py35,py36,pypy,pypy3
|
||||
# Mac OS X 10.10 Yosemite
|
||||
- os: osx
|
||||
osx_image: xcode6.4
|
||||
language: generic
|
||||
env: PY_VER=py26,py27,py33,py34,py35,py36,pypy,pypy3
|
||||
allow_failures:
|
||||
# PyPy3 on Travis CI is out of date
|
||||
- python: pypy3
|
||||
# Python nightly could fail
|
||||
- python: 3.7-dev
|
||||
- env: PY_VER=py37
|
||||
- env: PY_VER=py37/pyenv
|
||||
- env: PY_VER=py37 PY_ORIGIN=pyenv
|
||||
fast_finish: true
|
||||
|
||||
cache:
|
||||
- pip
|
||||
- directories:
|
||||
- $HOME/.pyenv.cache
|
||||
- $HOME/.bin
|
||||
|
||||
before_install:
|
||||
- source test/tools/ci-linux.sh
|
||||
- ci_step_before_install
|
||||
|
||||
install:
|
||||
- ci_step_install
|
||||
|
||||
script:
|
||||
- ci_step_script
|
||||
|
||||
after_success:
|
||||
- ci_step_success
|
||||
|
||||
after_failure:
|
||||
- ci_step_failure
|
||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (C) 2016 Andris Raugulis (moo@arthepsy.eu)
|
||||
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -1,6 +1,8 @@
|
||||
# ssh-audit
|
||||
[![build status](https://api.travis-ci.org/arthepsy/ssh-audit.svg)](https://travis-ci.org/arthepsy/ssh-audit)
|
||||
[![coverage status](https://coveralls.io/repos/github/arthepsy/ssh-audit/badge.svg)](https://coveralls.io/github/arthepsy/ssh-audit)
|
||||
[![travis build status](https://api.travis-ci.org/arthepsy/ssh-audit.svg?branch=develop)](https://travis-ci.org/arthepsy/ssh-audit)
|
||||
[![appveyor build status](https://ci.appveyor.com/api/projects/status/4m5r73m0r023edil/branch/develop?svg=true)](https://ci.appveyor.com/project/arthepsy/ssh-audit)
|
||||
[![codecov](https://codecov.io/gh/arthepsy/ssh-audit/branch/develop/graph/badge.svg)](https://codecov.io/gh/arthepsy/ssh-audit)
|
||||
[![Quality Gate](https://sonarqube.com/api/badges/gate?key=arthepsy-github%3Assh-audit%3Adevelop&template=ROUNDED)](https://sq.evolutiongaming.com/dashboard?id=arthepsy-github%3Assh-audit%3Adevelop)
|
||||
**ssh-audit** is a tool for ssh server auditing.
|
||||
|
||||
## Features
|
||||
|
1914
ssh-audit.py
1914
ssh-audit.py
File diff suppressed because it is too large
Load Diff
@ -40,6 +40,41 @@ def output_spy():
|
||||
return _OutputSpy()
|
||||
|
||||
|
||||
class _VirtualGlobalSocket(object):
|
||||
def __init__(self, vsocket):
|
||||
self.vsocket = vsocket
|
||||
self.addrinfodata = {}
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def create_connection(self, address, timeout=0, source_address=None):
|
||||
# pylint: disable=protected-access
|
||||
return self.vsocket._connect(address, True)
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def socket(self,
|
||||
family=socket.AF_INET,
|
||||
socktype=socket.SOCK_STREAM,
|
||||
proto=0,
|
||||
fileno=None):
|
||||
return self.vsocket
|
||||
|
||||
def getaddrinfo(self, host, port, family=0, socktype=0, proto=0, flags=0):
|
||||
key = '{0}#{1}'.format(host, port)
|
||||
if key in self.addrinfodata:
|
||||
data = self.addrinfodata[key]
|
||||
if isinstance(data, Exception):
|
||||
raise data
|
||||
return data
|
||||
if host == 'localhost':
|
||||
r = []
|
||||
if family in (0, socket.AF_INET):
|
||||
r.append((socket.AF_INET, 1, 6, '', ('127.0.0.1', port)))
|
||||
if family in (0, socket.AF_INET6):
|
||||
r.append((socket.AF_INET6, 1, 6, '', ('::1', port)))
|
||||
return r
|
||||
return []
|
||||
|
||||
|
||||
class _VirtualSocket(object):
|
||||
def __init__(self):
|
||||
self.sock_address = ('127.0.0.1', 0)
|
||||
@ -49,6 +84,7 @@ class _VirtualSocket(object):
|
||||
self.rdata = []
|
||||
self.sdata = []
|
||||
self.errors = {}
|
||||
self.gsock = _VirtualGlobalSocket(self)
|
||||
|
||||
def _check_err(self, method):
|
||||
method_error = self.errors.get(method)
|
||||
@ -113,18 +149,8 @@ class _VirtualSocket(object):
|
||||
@pytest.fixture()
|
||||
def virtual_socket(monkeypatch):
|
||||
vsocket = _VirtualSocket()
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def _socket(family=socket.AF_INET,
|
||||
socktype=socket.SOCK_STREAM,
|
||||
proto=0,
|
||||
fileno=None):
|
||||
return vsocket
|
||||
|
||||
def _cc(address, timeout=0, source_address=None):
|
||||
# pylint: disable=protected-access
|
||||
return vsocket._connect(address, True)
|
||||
|
||||
monkeypatch.setattr(socket, 'create_connection', _cc)
|
||||
monkeypatch.setattr(socket, 'socket', _socket)
|
||||
gsock = vsocket.gsock
|
||||
monkeypatch.setattr(socket, 'create_connection', gsock.create_connection)
|
||||
monkeypatch.setattr(socket, 'socket', gsock.socket)
|
||||
monkeypatch.setattr(socket, 'getaddrinfo', gsock.getaddrinfo)
|
||||
return vsocket
|
||||
|
@ -1,10 +0,0 @@
|
||||
#!/bin/sh
|
||||
_cdir=$(cd -- "$(dirname "$0")" && pwd)
|
||||
type py.test > /dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "err: py.test (Python testing framework) not found."
|
||||
exit 1
|
||||
fi
|
||||
cd -- "${_cdir}/.."
|
||||
mkdir -p html
|
||||
py.test -v --cov-report=html:html/coverage --cov=ssh-audit test
|
@ -1,10 +0,0 @@
|
||||
#!/bin/sh
|
||||
_cdir=$(cd -- "$(dirname "$0")" && pwd)
|
||||
type mypy > /dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "err: mypy (Optional Static Typing for Python) not found."
|
||||
exit 1
|
||||
fi
|
||||
_htmldir="${_cdir}/../html/mypy-py2"
|
||||
mkdir -p "${_htmldir}"
|
||||
mypy --python-version 2.7 --config-file "${_cdir}/mypy.ini" --html-report "${_htmldir}" "${_cdir}/../ssh-audit.py"
|
@ -1,10 +0,0 @@
|
||||
#!/bin/sh
|
||||
_cdir=$(cd -- "$(dirname "$0")" && pwd)
|
||||
type mypy > /dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "err: mypy (Optional Static Typing for Python) not found."
|
||||
exit 1
|
||||
fi
|
||||
_htmldir="${_cdir}/../html/mypy-py3"
|
||||
mkdir -p "${_htmldir}"
|
||||
mypy --python-version 3.5 --config-file "${_cdir}/mypy.ini" --html-report "${_htmldir}" "${_cdir}/../ssh-audit.py"
|
@ -1,9 +0,0 @@
|
||||
[mypy]
|
||||
silent_imports = True
|
||||
disallow_untyped_calls = True
|
||||
disallow_untyped_defs = True
|
||||
check_untyped_defs = True
|
||||
disallow-subclassing-any = True
|
||||
warn-incomplete-stub = True
|
||||
warn-redundant-casts = True
|
||||
|
@ -1,13 +0,0 @@
|
||||
#!/bin/sh
|
||||
_cdir=$(cd -- "$(dirname "$0")" && pwd)
|
||||
type prospector > /dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "err: prospector (Python Static Analysis) not found."
|
||||
exit 1
|
||||
fi
|
||||
if [ X"$1" == X"" ]; then
|
||||
_file="${_cdir}/../ssh-audit.py"
|
||||
else
|
||||
_file="$1"
|
||||
fi
|
||||
prospector -E --profile-path "${_cdir}" -P prospector "${_file}"
|
@ -1,42 +0,0 @@
|
||||
strictness: veryhigh
|
||||
doc-warnings: false
|
||||
|
||||
pylint:
|
||||
disable:
|
||||
- multiple-imports
|
||||
- invalid-name
|
||||
- trailing-whitespace
|
||||
|
||||
options:
|
||||
max-args: 8 # default: 5
|
||||
max-locals: 20 # default: 15
|
||||
max-returns: 6
|
||||
max-branches: 15 # default: 12
|
||||
max-statements: 60 # default: 50
|
||||
max-parents: 7
|
||||
max-attributes: 8 # default: 7
|
||||
min-public-methods: 1 # default: 2
|
||||
max-public-methods: 20
|
||||
max-bool-expr: 5
|
||||
max-nested-blocks: 6 # default: 5
|
||||
max-line-length: 80 # default: 100
|
||||
ignore-long-lines: ^\s*(#\s+type:\s+.*|[A-Z0-9_]+\s+=\s+.*|('.*':\s+)?\[.*\],?)$
|
||||
max-module-lines: 2500 # default: 10000
|
||||
|
||||
pep8:
|
||||
disable:
|
||||
- W191 # indentation contains tabs
|
||||
- W293 # blank line contains whitespace
|
||||
- E101 # indentation contains mixed spaces and tabs
|
||||
- E401 # multiple imports on one line
|
||||
- E501 # line too long
|
||||
- E221 # multiple spaces before operator
|
||||
|
||||
pyflakes:
|
||||
disable:
|
||||
- F401 # module imported but unused
|
||||
- F821 # undefined name
|
||||
|
||||
mccabe:
|
||||
options:
|
||||
max-complexity: 15
|
6
test/stubs/colorama.pyi
Normal file
6
test/stubs/colorama.pyi
Normal file
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
from typing import Optional
|
||||
|
||||
def init(autoreset: bool = False, convert: Optional[bool] = None, strip: Optional[bool] = None, wrap: bool = True) -> None: ...
|
||||
|
@ -10,8 +10,8 @@ class TestAuditConf(object):
|
||||
self.AuditConf = ssh_audit.AuditConf
|
||||
self.usage = ssh_audit.usage
|
||||
|
||||
@classmethod
|
||||
def _test_conf(cls, conf, **kwargs):
|
||||
@staticmethod
|
||||
def _test_conf(conf, **kwargs):
|
||||
options = {
|
||||
'host': None,
|
||||
'port': 22,
|
||||
@ -20,7 +20,7 @@ class TestAuditConf(object):
|
||||
'batch': False,
|
||||
'colors': True,
|
||||
'verbose': False,
|
||||
'minlevel': 'info',
|
||||
'level': 'info',
|
||||
'ipv4': True,
|
||||
'ipv6': True,
|
||||
'ipvo': ()
|
||||
@ -34,7 +34,7 @@ class TestAuditConf(object):
|
||||
assert conf.batch is options['batch']
|
||||
assert conf.colors is options['colors']
|
||||
assert conf.verbose is options['verbose']
|
||||
assert conf.minlevel == options['minlevel']
|
||||
assert conf.level == options['level']
|
||||
assert conf.ipv4 == options['ipv4']
|
||||
assert conf.ipv6 == options['ipv6']
|
||||
assert conf.ipvo == options['ipvo']
|
||||
@ -115,14 +115,14 @@ class TestAuditConf(object):
|
||||
conf.ipvo = (4, 4, 4, 6, 6)
|
||||
assert conf.ipvo == (4, 6)
|
||||
|
||||
def test_audit_conf_minlevel(self):
|
||||
def test_audit_conf_level(self):
|
||||
conf = self.AuditConf()
|
||||
for level in ['info', 'warn', 'fail']:
|
||||
conf.minlevel = level
|
||||
assert conf.minlevel == level
|
||||
conf.level = level
|
||||
assert conf.level == level
|
||||
for level in ['head', 'good', 'unknown', None]:
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
conf.minlevel = level
|
||||
conf.level = level
|
||||
excinfo.match(r'.*invalid level.*')
|
||||
|
||||
def test_audit_conf_cmdline(self):
|
||||
@ -148,6 +148,14 @@ class TestAuditConf(object):
|
||||
self._test_conf(conf, host='localhost', port=2222)
|
||||
conf = c('-p 2222 localhost')
|
||||
self._test_conf(conf, host='localhost', port=2222)
|
||||
conf = c('2001:4860:4860::8888')
|
||||
self._test_conf(conf, host='2001:4860:4860::8888')
|
||||
conf = c('[2001:4860:4860::8888]:22')
|
||||
self._test_conf(conf, host='2001:4860:4860::8888')
|
||||
conf = c('[2001:4860:4860::8888]:2222')
|
||||
self._test_conf(conf, host='2001:4860:4860::8888', port=2222)
|
||||
conf = c('-p 2222 2001:4860:4860::8888')
|
||||
self._test_conf(conf, host='2001:4860:4860::8888', port=2222)
|
||||
with pytest.raises(SystemExit):
|
||||
conf = c('localhost:')
|
||||
with pytest.raises(SystemExit):
|
||||
@ -183,10 +191,10 @@ class TestAuditConf(object):
|
||||
conf = c('-v localhost')
|
||||
self._test_conf(conf, host='localhost', verbose=True)
|
||||
conf = c('-l info localhost')
|
||||
self._test_conf(conf, host='localhost', minlevel='info')
|
||||
self._test_conf(conf, host='localhost', level='info')
|
||||
conf = c('-l warn localhost')
|
||||
self._test_conf(conf, host='localhost', minlevel='warn')
|
||||
self._test_conf(conf, host='localhost', level='warn')
|
||||
conf = c('-l fail localhost')
|
||||
self._test_conf(conf, host='localhost', minlevel='fail')
|
||||
self._test_conf(conf, host='localhost', level='fail')
|
||||
with pytest.raises(SystemExit):
|
||||
conf = c('-l something localhost')
|
||||
|
@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import socket
|
||||
import errno
|
||||
import pytest
|
||||
|
||||
|
||||
@ -17,46 +18,99 @@ class TestErrors(object):
|
||||
conf.batch = True
|
||||
return conf
|
||||
|
||||
def _audit(self, spy, conf=None, sysexit=True):
|
||||
if conf is None:
|
||||
conf = self._conf()
|
||||
spy.begin()
|
||||
if sysexit:
|
||||
with pytest.raises(SystemExit):
|
||||
self.audit(conf)
|
||||
else:
|
||||
self.audit(conf)
|
||||
lines = spy.flush()
|
||||
return lines
|
||||
|
||||
def test_connection_unresolved(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.gsock.addrinfodata['localhost#22'] = []
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 1
|
||||
assert 'has no DNS records' in lines[-1]
|
||||
|
||||
def test_connection_refused(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.errors['connect'] = socket.error(61, 'Connection refused')
|
||||
output_spy.begin()
|
||||
with pytest.raises(SystemExit):
|
||||
self.audit(self._conf())
|
||||
lines = output_spy.flush()
|
||||
vsocket.errors['connect'] = socket.error(errno.ECONNREFUSED, 'Connection refused')
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 1
|
||||
assert 'Connection refused' in lines[-1]
|
||||
|
||||
def test_connection_closed_before_banner(self, output_spy, virtual_socket):
|
||||
def test_connection_timeout(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(socket.error(54, 'Connection reset by peer'))
|
||||
output_spy.begin()
|
||||
with pytest.raises(SystemExit):
|
||||
self.audit(self._conf())
|
||||
lines = output_spy.flush()
|
||||
vsocket.errors['connect'] = socket.timeout('timed out')
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 1
|
||||
assert 'timed out' in lines[-1]
|
||||
|
||||
def test_recv_empty(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 1
|
||||
assert 'did not receive banner' in lines[-1]
|
||||
|
||||
def test_recv_timeout(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(socket.timeout('timed out'))
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 1
|
||||
assert 'did not receive banner' in lines[-1]
|
||||
assert 'timed out' in lines[-1]
|
||||
|
||||
def test_recv_retry_till_timeout(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(socket.error(errno.EAGAIN, 'Resource temporarily unavailable'))
|
||||
vsocket.rdata.append(socket.error(errno.EWOULDBLOCK, 'Resource temporarily unavailable'))
|
||||
vsocket.rdata.append(socket.error(errno.EAGAIN, 'Resource temporarily unavailable'))
|
||||
vsocket.rdata.append(socket.timeout('timed out'))
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 1
|
||||
assert 'did not receive banner' in lines[-1]
|
||||
assert 'timed out' in lines[-1]
|
||||
|
||||
def test_recv_retry_till_reset(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(socket.error(errno.EAGAIN, 'Resource temporarily unavailable'))
|
||||
vsocket.rdata.append(socket.error(errno.EWOULDBLOCK, 'Resource temporarily unavailable'))
|
||||
vsocket.rdata.append(socket.error(errno.EAGAIN, 'Resource temporarily unavailable'))
|
||||
vsocket.rdata.append(socket.error(errno.ECONNRESET, 'Connection reset by peer'))
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 1
|
||||
assert 'did not receive banner' in lines[-1]
|
||||
assert 'reset by peer' in lines[-1]
|
||||
|
||||
def test_connection_closed_before_banner(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(socket.error(errno.ECONNRESET, 'Connection reset by peer'))
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 1
|
||||
assert 'did not receive banner' in lines[-1]
|
||||
assert 'reset by peer' in lines[-1]
|
||||
|
||||
def test_connection_closed_after_header(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(b'header line 1\n')
|
||||
vsocket.rdata.append(b'\n')
|
||||
vsocket.rdata.append(b'header line 2\n')
|
||||
vsocket.rdata.append(socket.error(54, 'Connection reset by peer'))
|
||||
output_spy.begin()
|
||||
with pytest.raises(SystemExit):
|
||||
self.audit(self._conf())
|
||||
lines = output_spy.flush()
|
||||
vsocket.rdata.append(socket.error(errno.ECONNRESET, 'Connection reset by peer'))
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 3
|
||||
assert 'did not receive banner' in lines[-1]
|
||||
assert 'reset by peer' in lines[-1]
|
||||
|
||||
def test_connection_closed_after_banner(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(b'SSH-2.0-ssh-audit-test\r\n')
|
||||
vsocket.rdata.append(socket.error(54, 'Connection reset by peer'))
|
||||
output_spy.begin()
|
||||
with pytest.raises(SystemExit):
|
||||
self.audit(self._conf())
|
||||
lines = output_spy.flush()
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 2
|
||||
assert 'error reading packet' in lines[-1]
|
||||
assert 'reset by peer' in lines[-1]
|
||||
@ -64,10 +118,7 @@ class TestErrors(object):
|
||||
def test_empty_data_after_banner(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(b'SSH-2.0-ssh-audit-test\r\n')
|
||||
output_spy.begin()
|
||||
with pytest.raises(SystemExit):
|
||||
self.audit(self._conf())
|
||||
lines = output_spy.flush()
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 2
|
||||
assert 'error reading packet' in lines[-1]
|
||||
assert 'empty' in lines[-1]
|
||||
@ -76,10 +127,7 @@ class TestErrors(object):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(b'SSH-2.0-ssh-audit-test\r\n')
|
||||
vsocket.rdata.append(b'xxx\n')
|
||||
output_spy.begin()
|
||||
with pytest.raises(SystemExit):
|
||||
self.audit(self._conf())
|
||||
lines = output_spy.flush()
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 2
|
||||
assert 'error reading packet' in lines[-1]
|
||||
assert 'xxx' in lines[-1]
|
||||
@ -87,10 +135,7 @@ class TestErrors(object):
|
||||
def test_non_ascii_banner(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(b'SSH-2.0-ssh-audit-test\xc3\xbc\r\n')
|
||||
output_spy.begin()
|
||||
with pytest.raises(SystemExit):
|
||||
self.audit(self._conf())
|
||||
lines = output_spy.flush()
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 3
|
||||
assert 'error reading packet' in lines[-1]
|
||||
assert 'ASCII' in lines[-2]
|
||||
@ -100,10 +145,7 @@ class TestErrors(object):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(b'SSH-2.0-ssh-audit-test\r\n')
|
||||
vsocket.rdata.append(b'\x81\xff\n')
|
||||
output_spy.begin()
|
||||
with pytest.raises(SystemExit):
|
||||
self.audit(self._conf())
|
||||
lines = output_spy.flush()
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 2
|
||||
assert 'error reading packet' in lines[-1]
|
||||
assert '\\x81\\xff' in lines[-1]
|
||||
@ -112,12 +154,9 @@ class TestErrors(object):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(b'SSH-1.3-ssh-audit-test\r\n')
|
||||
vsocket.rdata.append(b'Protocol major versions differ.\n')
|
||||
output_spy.begin()
|
||||
with pytest.raises(SystemExit):
|
||||
conf = self._conf()
|
||||
conf.ssh1, conf.ssh2 = True, False
|
||||
self.audit(conf)
|
||||
lines = output_spy.flush()
|
||||
lines = self._audit(output_spy, conf)
|
||||
assert len(lines) == 3
|
||||
assert 'error reading packet' in lines[-1]
|
||||
assert 'major versions differ' in lines[-1]
|
||||
|
@ -41,13 +41,13 @@ class TestOutput(object):
|
||||
out = self.Output()
|
||||
# default: on
|
||||
assert out.batch is False
|
||||
assert out.colors is True
|
||||
assert out.minlevel == 'info'
|
||||
assert out.use_colors is True
|
||||
assert out.level == 'info'
|
||||
|
||||
def test_output_colors(self, output_spy):
|
||||
out = self.Output()
|
||||
# test without colors
|
||||
out.colors = False
|
||||
out.use_colors = False
|
||||
output_spy.begin()
|
||||
out.info('info color')
|
||||
assert output_spy.flush() == [u'info color']
|
||||
@ -66,7 +66,7 @@ class TestOutput(object):
|
||||
if not out.colors_supported:
|
||||
return
|
||||
# test with colors
|
||||
out.colors = True
|
||||
out.use_colors = True
|
||||
output_spy.begin()
|
||||
out.info('info color')
|
||||
assert output_spy.flush() == [u'info color']
|
||||
@ -93,29 +93,29 @@ class TestOutput(object):
|
||||
|
||||
def test_output_levels(self):
|
||||
out = self.Output()
|
||||
assert out.getlevel('info') == 0
|
||||
assert out.getlevel('good') == 0
|
||||
assert out.getlevel('warn') == 1
|
||||
assert out.getlevel('fail') == 2
|
||||
assert out.getlevel('unknown') > 2
|
||||
assert out.get_level('info') == 0
|
||||
assert out.get_level('good') == 0
|
||||
assert out.get_level('warn') == 1
|
||||
assert out.get_level('fail') == 2
|
||||
assert out.get_level('unknown') > 2
|
||||
|
||||
def test_output_minlevel_property(self):
|
||||
def test_output_level_property(self):
|
||||
out = self.Output()
|
||||
out.minlevel = 'info'
|
||||
assert out.minlevel == 'info'
|
||||
out.minlevel = 'good'
|
||||
assert out.minlevel == 'info'
|
||||
out.minlevel = 'warn'
|
||||
assert out.minlevel == 'warn'
|
||||
out.minlevel = 'fail'
|
||||
assert out.minlevel == 'fail'
|
||||
out.minlevel = 'invalid level'
|
||||
assert out.minlevel == 'unknown'
|
||||
out.level = 'info'
|
||||
assert out.level == 'info'
|
||||
out.level = 'good'
|
||||
assert out.level == 'info'
|
||||
out.level = 'warn'
|
||||
assert out.level == 'warn'
|
||||
out.level = 'fail'
|
||||
assert out.level == 'fail'
|
||||
out.level = 'invalid level'
|
||||
assert out.level == 'unknown'
|
||||
|
||||
def test_output_minlevel(self, output_spy):
|
||||
def test_output_level(self, output_spy):
|
||||
out = self.Output()
|
||||
# visible: all
|
||||
out.minlevel = 'info'
|
||||
out.level = 'info'
|
||||
output_spy.begin()
|
||||
out.info('info color')
|
||||
out.head('head color')
|
||||
@ -124,7 +124,7 @@ class TestOutput(object):
|
||||
out.fail('fail color')
|
||||
assert len(output_spy.flush()) == 5
|
||||
# visible: head, warn, fail
|
||||
out.minlevel = 'warn'
|
||||
out.level = 'warn'
|
||||
output_spy.begin()
|
||||
out.info('info color')
|
||||
out.head('head color')
|
||||
@ -133,7 +133,7 @@ class TestOutput(object):
|
||||
out.fail('fail color')
|
||||
assert len(output_spy.flush()) == 3
|
||||
# visible: head, fail
|
||||
out.minlevel = 'fail'
|
||||
out.level = 'fail'
|
||||
output_spy.begin()
|
||||
out.info('info color')
|
||||
out.head('head color')
|
||||
@ -142,7 +142,7 @@ class TestOutput(object):
|
||||
out.fail('fail color')
|
||||
assert len(output_spy.flush()) == 2
|
||||
# visible: head
|
||||
out.minlevel = 'invalid level'
|
||||
out.level = 'invalid level'
|
||||
output_spy.begin()
|
||||
out.info('info color')
|
||||
out.head('head color')
|
||||
@ -155,7 +155,7 @@ class TestOutput(object):
|
||||
out = self.Output()
|
||||
# visible: all
|
||||
output_spy.begin()
|
||||
out.minlevel = 'info'
|
||||
out.level = 'info'
|
||||
out.batch = False
|
||||
out.info('info color')
|
||||
out.head('head color')
|
||||
@ -165,7 +165,7 @@ class TestOutput(object):
|
||||
assert len(output_spy.flush()) == 5
|
||||
# visible: all except head
|
||||
output_spy.begin()
|
||||
out.minlevel = 'info'
|
||||
out.level = 'info'
|
||||
out.batch = True
|
||||
out.info('info color')
|
||||
out.head('head color')
|
||||
|
85
test/test_resolve.py
Normal file
85
test/test_resolve.py
Normal file
@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import socket
|
||||
import pytest
|
||||
|
||||
|
||||
# pylint: disable=attribute-defined-outside-init,protected-access
|
||||
class TestResolve(object):
|
||||
@pytest.fixture(autouse=True)
|
||||
def init(self, ssh_audit):
|
||||
self.AuditConf = ssh_audit.AuditConf
|
||||
self.audit = ssh_audit.audit
|
||||
self.ssh = ssh_audit.SSH
|
||||
|
||||
def _conf(self):
|
||||
conf = self.AuditConf('localhost', 22)
|
||||
conf.colors = False
|
||||
conf.batch = True
|
||||
return conf
|
||||
|
||||
def test_resolve_error(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.gsock.addrinfodata['localhost#22'] = socket.gaierror(8, 'hostname nor servname provided, or not known')
|
||||
s = self.ssh.Socket('localhost', 22)
|
||||
conf = self._conf()
|
||||
output_spy.begin()
|
||||
with pytest.raises(SystemExit):
|
||||
r = list(s._resolve(conf.ipvo))
|
||||
lines = output_spy.flush()
|
||||
assert len(lines) == 1
|
||||
assert 'hostname nor servname provided' in lines[-1]
|
||||
|
||||
def test_resolve_hostname_without_records(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.gsock.addrinfodata['localhost#22'] = []
|
||||
s = self.ssh.Socket('localhost', 22)
|
||||
conf = self._conf()
|
||||
output_spy.begin()
|
||||
r = list(s._resolve(conf.ipvo))
|
||||
assert len(r) == 0
|
||||
|
||||
def test_resolve_ipv4(self, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
conf = self._conf()
|
||||
conf.ipv4 = True
|
||||
s = self.ssh.Socket('localhost', 22)
|
||||
r = list(s._resolve(conf.ipvo))
|
||||
assert len(r) == 1
|
||||
assert r[0] == (socket.AF_INET, ('127.0.0.1', 22))
|
||||
|
||||
def test_resolve_ipv6(self, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
s = self.ssh.Socket('localhost', 22)
|
||||
conf = self._conf()
|
||||
conf.ipv6 = True
|
||||
r = list(s._resolve(conf.ipvo))
|
||||
assert len(r) == 1
|
||||
assert r[0] == (socket.AF_INET6, ('::1', 22))
|
||||
|
||||
def test_resolve_ipv46_both(self, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
s = self.ssh.Socket('localhost', 22)
|
||||
conf = self._conf()
|
||||
r = list(s._resolve(conf.ipvo))
|
||||
assert len(r) == 2
|
||||
assert r[0] == (socket.AF_INET, ('127.0.0.1', 22))
|
||||
assert r[1] == (socket.AF_INET6, ('::1', 22))
|
||||
|
||||
def test_resolve_ipv46_order(self, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
s = self.ssh.Socket('localhost', 22)
|
||||
conf = self._conf()
|
||||
conf.ipv4 = True
|
||||
conf.ipv6 = True
|
||||
r = list(s._resolve(conf.ipvo))
|
||||
assert len(r) == 2
|
||||
assert r[0] == (socket.AF_INET, ('127.0.0.1', 22))
|
||||
assert r[1] == (socket.AF_INET6, ('::1', 22))
|
||||
conf = self._conf()
|
||||
conf.ipv6 = True
|
||||
conf.ipv4 = True
|
||||
r = list(s._resolve(conf.ipvo))
|
||||
assert len(r) == 2
|
||||
assert r[0] == (socket.AF_INET6, ('::1', 22))
|
||||
assert r[1] == (socket.AF_INET, ('127.0.0.1', 22))
|
41
test/test_socket.py
Normal file
41
test/test_socket.py
Normal file
@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import socket
|
||||
import pytest
|
||||
|
||||
|
||||
# pylint: disable=attribute-defined-outside-init
|
||||
class TestSocket(object):
|
||||
@pytest.fixture(autouse=True)
|
||||
def init(self, ssh_audit):
|
||||
self.ssh = ssh_audit.SSH
|
||||
|
||||
def test_invalid_host(self, virtual_socket):
|
||||
with pytest.raises(ValueError):
|
||||
s = self.ssh.Socket(None, 22)
|
||||
|
||||
def test_invalid_port(self, virtual_socket):
|
||||
with pytest.raises(ValueError):
|
||||
s = self.ssh.Socket('localhost', 'abc')
|
||||
with pytest.raises(ValueError):
|
||||
s = self.ssh.Socket('localhost', -1)
|
||||
with pytest.raises(ValueError):
|
||||
s = self.ssh.Socket('localhost', 0)
|
||||
with pytest.raises(ValueError):
|
||||
s = self.ssh.Socket('localhost', 65536)
|
||||
|
||||
def test_not_connected_socket(self, virtual_socket):
|
||||
sock = self.ssh.Socket('localhost', 22)
|
||||
banner, header, err = sock.get_banner()
|
||||
assert banner is None
|
||||
assert len(header) == 0
|
||||
assert err == 'not connected'
|
||||
s, e = sock.recv()
|
||||
assert s == -1
|
||||
assert e == 'not connected'
|
||||
s, e = sock.send('nothing')
|
||||
assert s == -1
|
||||
assert e == 'not connected'
|
||||
s, e = sock.send_packet()
|
||||
assert s == -1
|
||||
assert e == 'not connected'
|
@ -168,17 +168,17 @@ class TestSoftware(object):
|
||||
assert s.display(True) == str(s)
|
||||
assert s.display(False) == str(s)
|
||||
assert repr(s) == '<Software(product=libssh, version=0.2)>'
|
||||
s = ps('SSH-2.0-libssh-0.7.3')
|
||||
s = ps('SSH-2.0-libssh-0.7.4')
|
||||
assert s.vendor is None
|
||||
assert s.product == 'libssh'
|
||||
assert s.version == '0.7.3'
|
||||
assert s.version == '0.7.4'
|
||||
assert s.patch is None
|
||||
assert s.os is None
|
||||
assert str(s) == 'libssh 0.7.3'
|
||||
assert str(s) == 'libssh 0.7.4'
|
||||
assert str(s) == s.display()
|
||||
assert s.display(True) == str(s)
|
||||
assert s.display(False) == str(s)
|
||||
assert repr(s) == '<Software(product=libssh, version=0.7.3)>'
|
||||
assert repr(s) == '<Software(product=libssh, version=0.7.4)>'
|
||||
|
||||
def test_romsshell_software(self):
|
||||
ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x)) # noqa
|
||||
|
@ -66,34 +66,51 @@ class TestSSH1(object):
|
||||
assert fp.md5 == 'MD5:9d:26:f8:39:fc:20:9d:9b:ca:cc:4a:0f:e1:93:f5:96'
|
||||
assert fp.sha256 == 'SHA256:vZdx3mhzbvVJmn08t/ruv8WDhJ9jfKYsCTuSzot+QIs'
|
||||
|
||||
def test_pkm_read(self):
|
||||
pkm = self.ssh1.PublicKeyMessage.parse(self._pkm_payload())
|
||||
assert pkm is not None
|
||||
assert pkm.cookie == b'\x88\x99\xaa\xbb\xcc\xdd\xee\xff'
|
||||
b, e, m = self._server_key()
|
||||
def _assert_pkm_keys(self, pkm, skey, hkey):
|
||||
b, e, m = skey
|
||||
assert pkm.server_key_bits == b
|
||||
assert pkm.server_key_public_exponent == e
|
||||
assert pkm.server_key_public_modulus == m
|
||||
b, e, m = self._host_key()
|
||||
b, e, m = hkey
|
||||
assert pkm.host_key_bits == b
|
||||
assert pkm.host_key_public_exponent == e
|
||||
assert pkm.host_key_public_modulus == m
|
||||
fp = self.ssh.Fingerprint(pkm.host_key_fingerprint_data)
|
||||
|
||||
def _assert_pkm_fields(self, pkm, skey, hkey):
|
||||
assert pkm is not None
|
||||
assert pkm.cookie == b'\x88\x99\xaa\xbb\xcc\xdd\xee\xff'
|
||||
self._assert_pkm_keys(pkm, skey, hkey)
|
||||
assert pkm.protocol_flags == 2
|
||||
assert pkm.supported_ciphers_mask == 72
|
||||
assert pkm.supported_ciphers == ['3des', 'blowfish']
|
||||
assert pkm.supported_authentications_mask == 36
|
||||
assert pkm.supported_authentications == ['rsa', 'tis']
|
||||
fp = self.ssh.Fingerprint(pkm.host_key_fingerprint_data)
|
||||
assert fp.md5 == 'MD5:9d:26:f8:39:fc:20:9d:9b:ca:cc:4a:0f:e1:93:f5:96'
|
||||
assert fp.sha256 == 'SHA256:vZdx3mhzbvVJmn08t/ruv8WDhJ9jfKYsCTuSzot+QIs'
|
||||
|
||||
def test_pkm_init(self):
|
||||
cookie = b'\x88\x99\xaa\xbb\xcc\xdd\xee\xff'
|
||||
pflags, cmask, amask = 2, 72, 36
|
||||
skey, hkey = self._server_key(), self._host_key()
|
||||
pkm = self.ssh1.PublicKeyMessage(cookie, skey, hkey, pflags, cmask, amask)
|
||||
self._assert_pkm_fields(pkm, skey, hkey)
|
||||
for skey2 in ([], [0], [0,1], [0,1,2,3]):
|
||||
with pytest.raises(ValueError):
|
||||
pkm = self.ssh1.PublicKeyMessage(cookie, skey2, hkey, pflags, cmask, amask)
|
||||
for hkey2 in ([], [0], [0,1], [0,1,2,3]):
|
||||
with pytest.raises(ValueError):
|
||||
print(hkey2)
|
||||
pkm = self.ssh1.PublicKeyMessage(cookie, skey, hkey2, pflags, cmask, amask)
|
||||
|
||||
def test_pkm_read(self):
|
||||
pkm = self.ssh1.PublicKeyMessage.parse(self._pkm_payload())
|
||||
self._assert_pkm_fields(pkm, self._server_key(), self._host_key())
|
||||
|
||||
def test_pkm_payload(self):
|
||||
cookie = b'\x88\x99\xaa\xbb\xcc\xdd\xee\xff'
|
||||
skey = self._server_key()
|
||||
hkey = self._host_key()
|
||||
pflags = 2
|
||||
cmask = 72
|
||||
amask = 36
|
||||
skey, hkey = self._server_key(), self._host_key()
|
||||
pflags, cmask, amask = 2, 72, 36
|
||||
pkm1 = self.ssh1.PublicKeyMessage(cookie, skey, hkey, pflags, cmask, amask)
|
||||
pkm2 = self.ssh1.PublicKeyMessage.parse(self._pkm_payload())
|
||||
assert pkm1.payload == pkm2.payload
|
||||
@ -108,7 +125,7 @@ class TestSSH1(object):
|
||||
output_spy.begin()
|
||||
self.audit(self._conf())
|
||||
lines = output_spy.flush()
|
||||
assert len(lines) == 10
|
||||
assert len(lines) == 13
|
||||
|
||||
def test_ssh1_server_invalid_first_packet(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
@ -121,7 +138,7 @@ class TestSSH1(object):
|
||||
with pytest.raises(SystemExit):
|
||||
self.audit(self._conf())
|
||||
lines = output_spy.flush()
|
||||
assert len(lines) == 4
|
||||
assert len(lines) == 7
|
||||
assert 'unknown message' in lines[-1]
|
||||
|
||||
def test_ssh1_server_invalid_checksum(self, output_spy, virtual_socket):
|
||||
|
164
test/test_ssh_algorithm.py
Normal file
164
test/test_ssh_algorithm.py
Normal file
@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import pytest
|
||||
|
||||
|
||||
# pylint: disable=attribute-defined-outside-init
|
||||
class TestSSHAlgorithm(object):
|
||||
@pytest.fixture(autouse=True)
|
||||
def init(self, ssh_audit):
|
||||
self.ssh = ssh_audit.SSH
|
||||
|
||||
def _tf(self, v, s=None):
|
||||
return self.ssh.Algorithm.Timeframe().update(v, s)
|
||||
|
||||
def test_get_ssh_version(self):
|
||||
def ver(v):
|
||||
return self.ssh.Algorithm.get_ssh_version(v)
|
||||
|
||||
assert ver('7.5') == ('OpenSSH', '7.5', False)
|
||||
assert ver('7.5C') == ('OpenSSH', '7.5', True)
|
||||
assert ver('d2016.74') == ('Dropbear SSH', '2016.74', False)
|
||||
assert ver('l10.7.4') == ('libssh', '0.7.4', False)
|
||||
assert ver('')[1] == ''
|
||||
|
||||
def test_get_since_text(self):
|
||||
def gst(v):
|
||||
return self.ssh.Algorithm.get_since_text(v)
|
||||
|
||||
assert gst(['7.5']) == 'available since OpenSSH 7.5'
|
||||
assert gst(['7.5C']) == 'available since OpenSSH 7.5 (client only)'
|
||||
assert gst(['7.5,']) == 'available since OpenSSH 7.5'
|
||||
assert gst(['d2016.73']) == 'available since Dropbear SSH 2016.73'
|
||||
assert gst(['7.5,d2016.73']) == 'available since OpenSSH 7.5, Dropbear SSH 2016.73'
|
||||
assert gst(['l10.7.4']) is None
|
||||
assert gst([]) is None
|
||||
|
||||
def test_timeframe_creation(self):
|
||||
# pylint: disable=line-too-long,too-many-statements
|
||||
def cmp_tf(v, s, r):
|
||||
assert str(self._tf(v, s)) == str(r)
|
||||
|
||||
cmp_tf(['6.2'], None, {'OpenSSH': ['6.2', None, '6.2', None]})
|
||||
cmp_tf(['6.2'], True, {'OpenSSH': ['6.2', None, None, None]})
|
||||
cmp_tf(['6.2'], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.2C'], None, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.2C'], True, {})
|
||||
cmp_tf(['6.2C'], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.1,6.2C'], None, {'OpenSSH': ['6.1', None, '6.2', None]})
|
||||
cmp_tf(['6.1,6.2C'], True, {'OpenSSH': ['6.1', None, None, None]})
|
||||
cmp_tf(['6.1,6.2C'], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.2C,6.1'], None, {'OpenSSH': ['6.1', None, '6.2', None]})
|
||||
cmp_tf(['6.2C,6.1'], True, {'OpenSSH': ['6.1', None, None, None]})
|
||||
cmp_tf(['6.2C,6.1'], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.3,6.2C'], None, {'OpenSSH': ['6.3', None, '6.2', None]})
|
||||
cmp_tf(['6.3,6.2C'], True, {'OpenSSH': ['6.3', None, None, None]})
|
||||
cmp_tf(['6.3,6.2C'], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.2C,6.3'], None, {'OpenSSH': ['6.3', None, '6.2', None]})
|
||||
cmp_tf(['6.2C,6.3'], True, {'OpenSSH': ['6.3', None, None, None]})
|
||||
cmp_tf(['6.2C,6.3'], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
|
||||
cmp_tf(['6.2', '6.6'], None, {'OpenSSH': ['6.2', '6.6', '6.2', '6.6']})
|
||||
cmp_tf(['6.2', '6.6'], True, {'OpenSSH': ['6.2', '6.6', None, None]})
|
||||
cmp_tf(['6.2', '6.6'], False, {'OpenSSH': [None, None, '6.2', '6.6']})
|
||||
cmp_tf(['6.2C', '6.6'], None, {'OpenSSH': [None, '6.6', '6.2', '6.6']})
|
||||
cmp_tf(['6.2C', '6.6'], True, {'OpenSSH': [None, '6.6', None, None]})
|
||||
cmp_tf(['6.2C', '6.6'], False, {'OpenSSH': [None, None, '6.2', '6.6']})
|
||||
cmp_tf(['6.1,6.2C', '6.6'], None, {'OpenSSH': ['6.1', '6.6', '6.2', '6.6']})
|
||||
cmp_tf(['6.1,6.2C', '6.6'], True, {'OpenSSH': ['6.1', '6.6', None, None]})
|
||||
cmp_tf(['6.1,6.2C', '6.6'], False, {'OpenSSH': [None, None, '6.2', '6.6']})
|
||||
cmp_tf(['6.2C,6.1', '6.6'], None, {'OpenSSH': ['6.1', '6.6', '6.2', '6.6']})
|
||||
cmp_tf(['6.2C,6.1', '6.6'], True, {'OpenSSH': ['6.1', '6.6', None, None]})
|
||||
cmp_tf(['6.2C,6.1', '6.6'], False, {'OpenSSH': [None, None, '6.2', '6.6']})
|
||||
cmp_tf(['6.3,6.2C', '6.6'], None, {'OpenSSH': ['6.3', '6.6', '6.2', '6.6']})
|
||||
cmp_tf(['6.3,6.2C', '6.6'], True, {'OpenSSH': ['6.3', '6.6', None, None]})
|
||||
cmp_tf(['6.3,6.2C', '6.6'], False, {'OpenSSH': [None, None, '6.2', '6.6']})
|
||||
cmp_tf(['6.2C,6.3', '6.6'], None, {'OpenSSH': ['6.3', '6.6', '6.2', '6.6']})
|
||||
cmp_tf(['6.2C,6.3', '6.6'], True, {'OpenSSH': ['6.3', '6.6', None, None]})
|
||||
cmp_tf(['6.2C,6.3', '6.6'], False, {'OpenSSH': [None, None, '6.2', '6.6']})
|
||||
|
||||
cmp_tf(['6.2', '6.6', None], None, {'OpenSSH': ['6.2', '6.6', '6.2', None]})
|
||||
cmp_tf(['6.2', '6.6', None], True, {'OpenSSH': ['6.2', '6.6', None, None]})
|
||||
cmp_tf(['6.2', '6.6', None], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.2C', '6.6', None], None, {'OpenSSH': [None, '6.6', '6.2', None]})
|
||||
cmp_tf(['6.2C', '6.6', None], True, {'OpenSSH': [None, '6.6', None, None]})
|
||||
cmp_tf(['6.2C', '6.6', None], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.1,6.2C', '6.6', None], None, {'OpenSSH': ['6.1', '6.6', '6.2', None]})
|
||||
cmp_tf(['6.1,6.2C', '6.6', None], True, {'OpenSSH': ['6.1', '6.6', None, None]})
|
||||
cmp_tf(['6.1,6.2C', '6.6', None], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.2C,6.1', '6.6', None], None, {'OpenSSH': ['6.1', '6.6', '6.2', None]})
|
||||
cmp_tf(['6.2C,6.1', '6.6', None], True, {'OpenSSH': ['6.1', '6.6', None, None]})
|
||||
cmp_tf(['6.2C,6.1', '6.6', None], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.2,6.3C', '6.6', None], None, {'OpenSSH': ['6.2', '6.6', '6.3', None]})
|
||||
cmp_tf(['6.2,6.3C', '6.6', None], True, {'OpenSSH': ['6.2', '6.6', None, None]})
|
||||
cmp_tf(['6.2,6.3C', '6.6', None], False, {'OpenSSH': [None, None, '6.3', None]})
|
||||
cmp_tf(['6.3C,6.2', '6.6', None], None, {'OpenSSH': ['6.2', '6.6', '6.3', None]})
|
||||
cmp_tf(['6.3C,6.2', '6.6', None], True, {'OpenSSH': ['6.2', '6.6', None, None]})
|
||||
cmp_tf(['6.3C,6.2', '6.6', None], False, {'OpenSSH': [None, None, '6.3', None]})
|
||||
|
||||
cmp_tf(['6.2', '6.6', '7.1'], None, {'OpenSSH': ['6.2', '6.6', '6.2', '7.1']})
|
||||
cmp_tf(['6.2', '6.6', '7.1'], True, {'OpenSSH': ['6.2', '6.6', None, None]})
|
||||
cmp_tf(['6.2', '6.6', '7.1'], False, {'OpenSSH': [None, None, '6.2', '7.1']})
|
||||
cmp_tf(['6.1,6.2C', '6.6', '7.1'], None, {'OpenSSH': ['6.1', '6.6', '6.2', '7.1']})
|
||||
cmp_tf(['6.1,6.2C', '6.6', '7.1'], True, {'OpenSSH': ['6.1', '6.6', None, None]})
|
||||
cmp_tf(['6.1,6.2C', '6.6', '7.1'], False, {'OpenSSH': [None, None, '6.2', '7.1']})
|
||||
cmp_tf(['6.2C,6.1', '6.6', '7.1'], None, {'OpenSSH': ['6.1', '6.6', '6.2', '7.1']})
|
||||
cmp_tf(['6.2C,6.1', '6.6', '7.1'], True, {'OpenSSH': ['6.1', '6.6', None, None]})
|
||||
cmp_tf(['6.2C,6.1', '6.6', '7.1'], False, {'OpenSSH': [None, None, '6.2', '7.1']})
|
||||
cmp_tf(['6.2,6.3C', '6.6', '7.1'], None, {'OpenSSH': ['6.2', '6.6', '6.3', '7.1']})
|
||||
cmp_tf(['6.2,6.3C', '6.6', '7.1'], True, {'OpenSSH': ['6.2', '6.6', None, None]})
|
||||
cmp_tf(['6.2,6.3C', '6.6', '7.1'], False, {'OpenSSH': [None, None, '6.3', '7.1']})
|
||||
cmp_tf(['6.3C,6.2', '6.6', '7.1'], None, {'OpenSSH': ['6.2', '6.6', '6.3', '7.1']})
|
||||
cmp_tf(['6.3C,6.2', '6.6', '7.1'], True, {'OpenSSH': ['6.2', '6.6', None, None]})
|
||||
cmp_tf(['6.3C,6.2', '6.6', '7.1'], False, {'OpenSSH': [None, None, '6.3', '7.1']})
|
||||
|
||||
tf1 = self._tf(['6.1,d2016.72,6.2C', '6.6,d2016.73', '7.1,d2016.74'])
|
||||
tf2 = self._tf(['d2016.72,6.2C,6.1', 'd2016.73,6.6', 'd2016.74,7.1'])
|
||||
tf3 = self._tf(['d2016.72,6.2C,6.1', '6.6,d2016.73', '7.1,d2016.74'])
|
||||
# check without caring for output order
|
||||
ov = "'OpenSSH': ['6.1', '6.6', '6.2', '7.1']"
|
||||
dv = "'Dropbear SSH': ['2016.72', '2016.73', '2016.72', '2016.74']"
|
||||
assert len(str(tf1)) == len(str(tf2)) == len(str(tf3))
|
||||
assert ov in str(tf1) and ov in str(tf2) and ov in str(tf3)
|
||||
assert dv in str(tf1) and dv in str(tf2) and dv in str(tf3)
|
||||
assert ov in repr(tf1) and ov in repr(tf2) and ov in repr(tf3)
|
||||
assert dv in repr(tf1) and dv in repr(tf2) and dv in repr(tf3)
|
||||
|
||||
def test_timeframe_object(self):
|
||||
tf = self._tf(['6.1,6.2C', '6.6', '7.1'])
|
||||
assert 'OpenSSH' in tf
|
||||
assert 'Dropbear SSH' not in tf
|
||||
assert 'libssh' not in tf
|
||||
assert 'unknown' not in tf
|
||||
assert tf['OpenSSH'] == ('6.1', '6.6', '6.2', '7.1')
|
||||
assert tf['Dropbear SSH'] == (None, None, None, None)
|
||||
assert tf['libssh'] == (None, None, None, None)
|
||||
assert tf['unknown'] == (None, None, None, None)
|
||||
assert tf.get_from('OpenSSH', True) == '6.1'
|
||||
assert tf.get_till('OpenSSH', True) == '6.6'
|
||||
assert tf.get_from('OpenSSH', False) == '6.2'
|
||||
assert tf.get_till('OpenSSH', False) == '7.1'
|
||||
|
||||
tf = self._tf(['6.1,d2016.72,6.2C', '6.6,d2016.73', '7.1,d2016.74'])
|
||||
assert 'OpenSSH' in tf
|
||||
assert 'Dropbear SSH' in tf
|
||||
assert 'libssh' not in tf
|
||||
assert 'unknown' not in tf
|
||||
assert tf['OpenSSH'] == ('6.1', '6.6', '6.2', '7.1')
|
||||
assert tf['Dropbear SSH'] == ('2016.72', '2016.73', '2016.72', '2016.74')
|
||||
assert tf['libssh'] == (None, None, None, None)
|
||||
assert tf['unknown'] == (None, None, None, None)
|
||||
assert tf.get_from('OpenSSH', True) == '6.1'
|
||||
assert tf.get_till('OpenSSH', True) == '6.6'
|
||||
assert tf.get_from('OpenSSH', False) == '6.2'
|
||||
assert tf.get_till('OpenSSH', False) == '7.1'
|
||||
assert tf.get_from('Dropbear SSH', True) == '2016.72'
|
||||
assert tf.get_till('Dropbear SSH', True) == '2016.73'
|
||||
assert tf.get_from('Dropbear SSH', False) == '2016.72'
|
||||
assert tf.get_till('Dropbear SSH', False) == '2016.74'
|
||||
ov = "'OpenSSH': ['6.1', '6.6', '6.2', '7.1']"
|
||||
dv = "'Dropbear SSH': ['2016.72', '2016.73', '2016.72', '2016.74']"
|
||||
assert ov in str(tf)
|
||||
assert dv in str(tf)
|
||||
assert ov in repr(tf)
|
||||
assert dv in repr(tf)
|
218
test/test_utils.py
Normal file
218
test/test_utils.py
Normal file
@ -0,0 +1,218 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import sys
|
||||
import pytest
|
||||
|
||||
|
||||
# pylint: disable=attribute-defined-outside-init
|
||||
class TestUtils(object):
|
||||
@pytest.fixture(autouse=True)
|
||||
def init(self, ssh_audit):
|
||||
self.utils = ssh_audit.Utils
|
||||
self.PY3 = sys.version_info >= (3,)
|
||||
|
||||
def test_to_bytes_py2(self):
|
||||
if self.PY3:
|
||||
return
|
||||
# binary_type (native str, bytes as str)
|
||||
assert self.utils.to_bytes('fran\xc3\xa7ais') == 'fran\xc3\xa7ais'
|
||||
assert self.utils.to_bytes(b'fran\xc3\xa7ais') == 'fran\xc3\xa7ais'
|
||||
# text_type (unicode)
|
||||
assert self.utils.to_bytes(u'fran\xe7ais') == 'fran\xc3\xa7ais'
|
||||
# other
|
||||
with pytest.raises(TypeError):
|
||||
self.utils.to_bytes(123)
|
||||
|
||||
def test_to_bytes_py3(self):
|
||||
if not self.PY3:
|
||||
return
|
||||
# binary_type (bytes)
|
||||
assert self.utils.to_bytes(b'fran\xc3\xa7ais') == b'fran\xc3\xa7ais'
|
||||
# text_type (native str as unicode, unicode)
|
||||
assert self.utils.to_bytes('fran\xe7ais') == b'fran\xc3\xa7ais'
|
||||
assert self.utils.to_bytes(u'fran\xe7ais') == b'fran\xc3\xa7ais'
|
||||
# other
|
||||
with pytest.raises(TypeError):
|
||||
self.utils.to_bytes(123)
|
||||
|
||||
def test_to_utext_py2(self):
|
||||
if self.PY3:
|
||||
return
|
||||
# binary_type (native str, bytes as str)
|
||||
assert self.utils.to_utext('fran\xc3\xa7ais') == u'fran\xe7ais'
|
||||
assert self.utils.to_utext(b'fran\xc3\xa7ais') == u'fran\xe7ais'
|
||||
# text_type (unicode)
|
||||
assert self.utils.to_utext(u'fran\xe7ais') == u'fran\xe7ais'
|
||||
# other
|
||||
with pytest.raises(TypeError):
|
||||
self.utils.to_utext(123)
|
||||
|
||||
def test_to_utext_py3(self):
|
||||
if not self.PY3:
|
||||
return
|
||||
# binary_type (bytes)
|
||||
assert self.utils.to_utext(b'fran\xc3\xa7ais') == u'fran\xe7ais'
|
||||
# text_type (native str as unicode, unicode)
|
||||
assert self.utils.to_utext('fran\xe7ais') == 'fran\xe7ais'
|
||||
assert self.utils.to_utext(u'fran\xe7ais') == u'fran\xe7ais'
|
||||
# other
|
||||
with pytest.raises(TypeError):
|
||||
self.utils.to_utext(123)
|
||||
|
||||
def test_to_ntext_py2(self):
|
||||
if self.PY3:
|
||||
return
|
||||
# str (native str, bytes as str)
|
||||
assert self.utils.to_ntext('fran\xc3\xa7ais') == 'fran\xc3\xa7ais'
|
||||
assert self.utils.to_ntext(b'fran\xc3\xa7ais') == 'fran\xc3\xa7ais'
|
||||
# text_type (unicode)
|
||||
assert self.utils.to_ntext(u'fran\xe7ais') == 'fran\xc3\xa7ais'
|
||||
# other
|
||||
with pytest.raises(TypeError):
|
||||
self.utils.to_ntext(123)
|
||||
|
||||
def test_to_ntext_py3(self):
|
||||
if not self.PY3:
|
||||
return
|
||||
# str (native str)
|
||||
assert self.utils.to_ntext('fran\xc3\xa7ais') == 'fran\xc3\xa7ais'
|
||||
assert self.utils.to_ntext(u'fran\xe7ais') == 'fran\xe7ais'
|
||||
# binary_type (bytes)
|
||||
assert self.utils.to_ntext(b'fran\xc3\xa7ais') == 'fran\xe7ais'
|
||||
# other
|
||||
with pytest.raises(TypeError):
|
||||
self.utils.to_ntext(123)
|
||||
|
||||
def test_is_ascii_py2(self):
|
||||
if self.PY3:
|
||||
return
|
||||
# text_type (unicode)
|
||||
assert self.utils.is_ascii(u'francais') is True
|
||||
assert self.utils.is_ascii(u'fran\xe7ais') is False
|
||||
# str
|
||||
assert self.utils.is_ascii('francais') is True
|
||||
assert self.utils.is_ascii('fran\xc3\xa7ais') is False
|
||||
# other
|
||||
assert self.utils.is_ascii(123) is False
|
||||
|
||||
def test_is_ascii_py3(self):
|
||||
if not self.PY3:
|
||||
return
|
||||
# text_type (str)
|
||||
assert self.utils.is_ascii('francais') is True
|
||||
assert self.utils.is_ascii(u'francais') is True
|
||||
assert self.utils.is_ascii('fran\xe7ais') is False
|
||||
assert self.utils.is_ascii(u'fran\xe7ais') is False
|
||||
# other
|
||||
assert self.utils.is_ascii(123) is False
|
||||
|
||||
def test_to_ascii_py2(self):
|
||||
if self.PY3:
|
||||
return
|
||||
# text_type (unicode)
|
||||
assert self.utils.to_ascii(u'francais') == 'francais'
|
||||
assert self.utils.to_ascii(u'fran\xe7ais') == 'fran?ais'
|
||||
assert self.utils.to_ascii(u'fran\xe7ais', 'ignore') == 'franais'
|
||||
# str
|
||||
assert self.utils.to_ascii('francais') == 'francais'
|
||||
assert self.utils.to_ascii('fran\xc3\xa7ais') == 'fran??ais'
|
||||
assert self.utils.to_ascii('fran\xc3\xa7ais', 'ignore') == 'franais'
|
||||
with pytest.raises(TypeError):
|
||||
self.utils.to_ascii(123)
|
||||
|
||||
def test_to_ascii_py3(self):
|
||||
if not self.PY3:
|
||||
return
|
||||
# text_type (str)
|
||||
assert self.utils.to_ascii('francais') == 'francais'
|
||||
assert self.utils.to_ascii(u'francais') == 'francais'
|
||||
assert self.utils.to_ascii('fran\xe7ais') == 'fran?ais'
|
||||
assert self.utils.to_ascii('fran\xe7ais', 'ignore') == 'franais'
|
||||
assert self.utils.to_ascii(u'fran\xe7ais') == 'fran?ais'
|
||||
assert self.utils.to_ascii(u'fran\xe7ais', 'ignore') == 'franais'
|
||||
with pytest.raises(TypeError):
|
||||
self.utils.to_ascii(123)
|
||||
|
||||
def test_is_print_ascii_py2(self):
|
||||
if self.PY3:
|
||||
return
|
||||
# text_type (unicode)
|
||||
assert self.utils.is_print_ascii(u'francais') is True
|
||||
assert self.utils.is_print_ascii(u'francais\n') is False
|
||||
assert self.utils.is_print_ascii(u'fran\xe7ais') is False
|
||||
assert self.utils.is_print_ascii(u'fran\xe7ais\n') is False
|
||||
# str
|
||||
assert self.utils.is_print_ascii('francais') is True
|
||||
assert self.utils.is_print_ascii('francais\n') is False
|
||||
assert self.utils.is_print_ascii('fran\xc3\xa7ais') is False
|
||||
# other
|
||||
assert self.utils.is_print_ascii(123) is False
|
||||
|
||||
def test_is_print_ascii_py3(self):
|
||||
if not self.PY3:
|
||||
return
|
||||
# text_type (str)
|
||||
assert self.utils.is_print_ascii('francais') is True
|
||||
assert self.utils.is_print_ascii('francais\n') is False
|
||||
assert self.utils.is_print_ascii(u'francais') is True
|
||||
assert self.utils.is_print_ascii(u'francais\n') is False
|
||||
assert self.utils.is_print_ascii('fran\xe7ais') is False
|
||||
assert self.utils.is_print_ascii(u'fran\xe7ais') is False
|
||||
# other
|
||||
assert self.utils.is_print_ascii(123) is False
|
||||
|
||||
def test_to_print_ascii_py2(self):
|
||||
if self.PY3:
|
||||
return
|
||||
# text_type (unicode)
|
||||
assert self.utils.to_print_ascii(u'francais') == 'francais'
|
||||
assert self.utils.to_print_ascii(u'francais\n') == 'francais?'
|
||||
assert self.utils.to_print_ascii(u'fran\xe7ais') == 'fran?ais'
|
||||
assert self.utils.to_print_ascii(u'fran\xe7ais\n') == 'fran?ais?'
|
||||
assert self.utils.to_print_ascii(u'fran\xe7ais', 'ignore') == 'franais'
|
||||
assert self.utils.to_print_ascii(u'fran\xe7ais\n', 'ignore') == 'franais'
|
||||
# str
|
||||
assert self.utils.to_print_ascii('francais') == 'francais'
|
||||
assert self.utils.to_print_ascii('francais\n') == 'francais?'
|
||||
assert self.utils.to_print_ascii('fran\xc3\xa7ais') == 'fran??ais'
|
||||
assert self.utils.to_print_ascii('fran\xc3\xa7ais\n') == 'fran??ais?'
|
||||
assert self.utils.to_print_ascii('fran\xc3\xa7ais', 'ignore') == 'franais'
|
||||
assert self.utils.to_print_ascii('fran\xc3\xa7ais\n', 'ignore') == 'franais'
|
||||
with pytest.raises(TypeError):
|
||||
self.utils.to_print_ascii(123)
|
||||
|
||||
def test_to_print_ascii_py3(self):
|
||||
if not self.PY3:
|
||||
return
|
||||
# text_type (str)
|
||||
assert self.utils.to_print_ascii('francais') == 'francais'
|
||||
assert self.utils.to_print_ascii('francais\n') == 'francais?'
|
||||
assert self.utils.to_print_ascii(u'francais') == 'francais'
|
||||
assert self.utils.to_print_ascii(u'francais\n') == 'francais?'
|
||||
assert self.utils.to_print_ascii('fran\xe7ais') == 'fran?ais'
|
||||
assert self.utils.to_print_ascii('fran\xe7ais\n') == 'fran?ais?'
|
||||
assert self.utils.to_print_ascii('fran\xe7ais', 'ignore') == 'franais'
|
||||
assert self.utils.to_print_ascii('fran\xe7ais\n', 'ignore') == 'franais'
|
||||
assert self.utils.to_print_ascii(u'fran\xe7ais') == 'fran?ais'
|
||||
assert self.utils.to_print_ascii(u'fran\xe7ais\n') == 'fran?ais?'
|
||||
assert self.utils.to_print_ascii(u'fran\xe7ais', 'ignore') == 'franais'
|
||||
assert self.utils.to_print_ascii(u'fran\xe7ais\n', 'ignore') == 'franais'
|
||||
with pytest.raises(TypeError):
|
||||
self.utils.to_print_ascii(123)
|
||||
|
||||
def test_ctoi(self):
|
||||
assert self.utils.ctoi(123) == 123
|
||||
assert self.utils.ctoi('ABC') == 65
|
||||
|
||||
def test_parse_int(self):
|
||||
assert self.utils.parse_int(123) == 123
|
||||
assert self.utils.parse_int('123') == 123
|
||||
assert self.utils.parse_int(-123) == -123
|
||||
assert self.utils.parse_int('-123') == -123
|
||||
assert self.utils.parse_int('abc') == 0
|
||||
|
||||
def test_unique_seq(self):
|
||||
assert self.utils.unique_seq((1, 2, 2, 3, 3, 3)) == (1, 2, 3)
|
||||
assert self.utils.unique_seq((3, 3, 3, 2, 2, 1)) == (3, 2, 1)
|
||||
assert self.utils.unique_seq([1, 2, 2, 3, 3, 3]) == [1, 2, 3]
|
||||
assert self.utils.unique_seq([3, 3, 3, 2, 2, 1]) == [3, 2, 1]
|
@ -200,7 +200,7 @@ class TestVersionCompare(object):
|
||||
versions.append('0.5.{0}'.format(i))
|
||||
for i in range(0, 6):
|
||||
versions.append('0.6.{0}'.format(i))
|
||||
for i in range(0, 4):
|
||||
for i in range(0, 5):
|
||||
versions.append('0.7.{0}'.format(i))
|
||||
l = len(versions)
|
||||
for i in range(l):
|
||||
|
412
test/tools/ci-linux.sh
Executable file
412
test/tools/ci-linux.sh
Executable file
@ -0,0 +1,412 @@
|
||||
#!/bin/sh
|
||||
|
||||
CI_VERBOSE=1
|
||||
|
||||
ci_err_msg() { echo "[ci] error: $1" >&2; }
|
||||
ci_err() { [ $1 -ne 0 ] && ci_err_msg "$2" && exit 1; }
|
||||
ci_is_osx() { [ X"$(uname -s)" == X"Darwin" ]; }
|
||||
|
||||
ci_get_pypy_ver() {
|
||||
local _v="$1"
|
||||
[ -z "$_v" ] && _v=$(python -V 2>&1)
|
||||
case "$_v" in
|
||||
pypy-*|pypy2-*|pypy3-*|pypy3.*) echo "$_v"; return 0 ;;
|
||||
pypy|pypy2|pypy3) echo "$_v-unknown"; return 0 ;;
|
||||
esac
|
||||
echo "$_v" | tail -1 | grep -qi pypy
|
||||
if [ $? -eq 0 ]; then
|
||||
local _py_ver=$(echo "$_v" | head -1 | cut -d ' ' -sf 2)
|
||||
local _pypy_ver=$(echo "$_v" | tail -1 | cut -d ' ' -sf 2)
|
||||
[ -z "${_py_ver} " ] && _py_ver=2
|
||||
[ -z "${_pypy_ver}" ] && _pypy_ver="unknown"
|
||||
case "${_py_ver}" in
|
||||
2*) echo "pypy-${_pypy_ver}" ;;
|
||||
3.3*) echo "pypy3.3-${_pypy_ver}" ;;
|
||||
3.5*) echo "pypy3.5-${_pypy_ver}" ;;
|
||||
*) echo "pypy3-${_pypy_ver}" ;;
|
||||
esac
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
ci_get_py_ver() {
|
||||
local _v
|
||||
case "$1" in
|
||||
py26) _v=2.6.9 ;;
|
||||
py27) _v=2.7.13 ;;
|
||||
py33) _v=3.3.6 ;;
|
||||
py34) _v=3.4.6 ;;
|
||||
py35) _v=3.5.3 ;;
|
||||
py36) _v=3.6.1 ;;
|
||||
py37) _v=3.7-dev ;;
|
||||
pypy) ci_is_osx && _v=pypy2-5.7.0 || _v=pypy-portable-5.7.0 ;;
|
||||
pypy3) ci_is_osx && _v=pypy3.3-5.5-alpha || _v=pypy3-portable-5.7.0 ;;
|
||||
*)
|
||||
[ -z "$1" ] && set -- "$(python -V 2>&1)"
|
||||
_v=$(ci_get_pypy_ver "$1")
|
||||
[ -z "$_v" ] && _v=$(echo "$_v" | head -1 | cut -d ' ' -sf 2)
|
||||
;;
|
||||
esac
|
||||
echo "${_v}"
|
||||
return 0
|
||||
}
|
||||
|
||||
ci_get_py_env() {
|
||||
[ -z "$1" ] && set -- "$(python -V 2>&1)"
|
||||
case "$(ci_get_pypy_ver "$1")" in
|
||||
pypy|pypy2|pypy-*|pypy2-*) echo "pypy" ;;
|
||||
pypy3|pypy3*) echo "pypy3" ;;
|
||||
*)
|
||||
local _v=$(echo "$1" | head -1 | sed -e 's/[^0-9]//g' | cut -c1-2)
|
||||
echo "py${_v}"
|
||||
esac
|
||||
return 0
|
||||
}
|
||||
|
||||
ci_pyenv_setup() {
|
||||
[ ${CI_VERBOSE} -gt 0 ] && echo "[ci] install pyenv"
|
||||
rm -rf ~/.pyenv
|
||||
git clone --depth 1 https://github.com/yyuu/pyenv.git ~/.pyenv
|
||||
PYENV_ROOT=$HOME/.pyenv
|
||||
PATH="$HOME/.pyenv/bin:$PATH"
|
||||
eval "$(pyenv init -)"
|
||||
ci_err $? "failed to init pyenv"
|
||||
[ ${CI_VERBOSE} -gt 0 ] && echo "[ci] pyenv init: $(pyenv -v 2>&1)"
|
||||
return 0
|
||||
}
|
||||
|
||||
ci_pyenv_install() {
|
||||
CI_PYENV_CACHE=~/.pyenv.cache
|
||||
type pyenv > /dev/null 2>&1
|
||||
ci_err $? "pyenv not found"
|
||||
local _py_ver=$(ci_get_py_ver "$1")
|
||||
local _py_env=$(ci_get_py_env "${_py_ver}")
|
||||
local _nocache
|
||||
case "${_py_env}" in
|
||||
py37) _nocache=1 ;;
|
||||
esac
|
||||
[ ${CI_VERBOSE} -gt 0 ] && echo "[ci] pyenv install: ${_py_env}/${_py_ver}"
|
||||
[ -z "${PYENV_ROOT}" ] && PYENV_ROOT="$HOME/.pyenv"
|
||||
local _py_ver_dir="${PYENV_ROOT}/versions/${_py_ver}"
|
||||
local _py_ver_cached_dir="${CI_PYENV_CACHE}/${_py_ver}"
|
||||
if [ -z "${_nocache}" ]; then
|
||||
if [ ! -d "${_py_ver_dir}" ]; then
|
||||
if [ -d "${_py_ver_cached_dir}" ]; then
|
||||
[ ${CI_VERBOSE} -gt 0 ] && echo "[ci] pyenv reuse ${_py_ver}"
|
||||
ln -s "${_py_ver_cached_dir}" "${_py_ver_dir}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [ ! -d "${_py_ver_dir}" ]; then
|
||||
pyenv install -s "${_py_ver}"
|
||||
ci_err $? "pyenv failed to install ${_py_ver}"
|
||||
if [ -z "${_nocache}" ]; then
|
||||
[ ${CI_VERBOSE} -gt 0 ] && echo "[ci] pyenv cache ${_py_ver}"
|
||||
rm -rf -- "${_py_ver_cached_dir}"
|
||||
mkdir -p -- "${CI_PYENV_CACHE}"
|
||||
mv "${_py_ver_dir}" "${_py_ver_cached_dir}"
|
||||
ln -s "${_py_ver_cached_dir}" "${_py_ver_dir}"
|
||||
fi
|
||||
fi
|
||||
pyenv rehash
|
||||
return 0
|
||||
}
|
||||
|
||||
ci_pyenv_use() {
|
||||
type pyenv > /dev/null 2>&1
|
||||
ci_err $? "pyenv not found"
|
||||
local _py_ver=$(ci_get_py_ver "$1")
|
||||
pyenv shell "${_py_ver}"
|
||||
ci_err $? "pyenv could not use ${_py_ver}"
|
||||
[ ${CI_VERBOSE} -gt 0 ] && echo "[ci] pyenv using python: $(python -V 2>&1)"
|
||||
return 0
|
||||
}
|
||||
|
||||
ci_pip_setup() {
|
||||
local _py_ver=$(ci_get_py_ver "$1")
|
||||
local _py_env=$(ci_get_py_env "${_py_ver}")
|
||||
[ ${CI_VERBOSE} -gt 0 ] && echo "[ci] install pip/venv for ${_py_env}/${_py_ver}"
|
||||
PIPOPT=$(python -c 'import sys; print("" if hasattr(sys, "real_prefix") else "--user")')
|
||||
if [ -z "${_py_env##py2*}" ]; then
|
||||
curl -O https://bootstrap.pypa.io/get-pip.py
|
||||
python get-pip.py ${PIPOPT}
|
||||
ci_err $? "failed to install pip"
|
||||
fi
|
||||
if [ X"${_py_env}" == X"py26" ]; then
|
||||
python -c 'import pip; pip.main();' install ${PIPOPT} -U pip virtualenv
|
||||
else
|
||||
python -m pip install ${PIPOPT} -U pip virtualenv
|
||||
fi
|
||||
ci_err $? "failed to upgrade pip/venv" || return 0
|
||||
}
|
||||
|
||||
ci_venv_setup() {
|
||||
local _py_ver=$(ci_get_py_ver "$1")
|
||||
local _py_env=$(ci_get_py_env "${_py_ver}")
|
||||
[ ${CI_VERBOSE} -gt 0 ] && echo "[ci] create venv for ${_py_env}/${_py_ver}"
|
||||
local VENV_DIR=~/.venv/${_py_ver}
|
||||
mkdir -p -- ~/.venv
|
||||
rm -rf -- "${VENV_DIR}"
|
||||
if [ X"${_py_env}" == X"py26" ]; then
|
||||
python -c 'import virtualenv; virtualenv.main();' "${VENV_DIR}"
|
||||
else
|
||||
python -m virtualenv "${VENV_DIR}"
|
||||
fi
|
||||
ci_err $? "failed to create venv" || return 0
|
||||
}
|
||||
|
||||
ci_venv_use() {
|
||||
local _py_ver=$(ci_get_py_ver "$1")
|
||||
local _py_env=$(ci_get_py_env "${_py_ver}")
|
||||
local VENV_DIR=~/.venv/${_py_ver}
|
||||
. "${VENV_DIR}/bin/activate"
|
||||
ci_err $? "could not actiavte virtualenv"
|
||||
[ ${CI_VERBOSE} -gt 0 ] && echo "[ci] venv using python: $(python -V 2>&1)"
|
||||
return 0
|
||||
}
|
||||
|
||||
ci_get_filedir() {
|
||||
local _sdir=$(cd -- "$(dirname "$0")" && pwd)
|
||||
local _pdir=$(pwd)
|
||||
if [ -z "${_pdir##${_sdir}*}" ]; then
|
||||
_sdir="${_pdir}"
|
||||
fi
|
||||
local _first=1
|
||||
while [ X"${_sdir}" != X"/" ]; do
|
||||
if [ ${_first} -eq 1 ]; then
|
||||
_first=0
|
||||
local _f=$(find "${_sdir}" -name "$1" | head -1)
|
||||
if [ -n "${_f}" ]; then
|
||||
echo $(dirname -- "${_f}")
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
_f=$(find "${_sdir}" -mindepth 1 -maxdepth 1 -name "$1" | head -1)
|
||||
fi
|
||||
[ -n "${_f}" ] && echo "${_sdir}" && return 0
|
||||
_sdir=$(cd -- "${_sdir}/.." && pwd)
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
ci_sq_ensure_java() {
|
||||
type java >/dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
ci_err_msg "java not found"
|
||||
return 1
|
||||
fi
|
||||
local _java_ver=$(java -version 2>&1 | head -1 | sed -e 's/[^0-9\._]//g')
|
||||
if [ -z "${_java_ver##1.8*}" ]; then
|
||||
return 0
|
||||
fi
|
||||
ci_err_msg "unsupported java version: ${_java_ver}"
|
||||
return 1
|
||||
}
|
||||
|
||||
ci_sq_ensure_scanner() {
|
||||
local _cli_version="3.0.0.702"
|
||||
local _cli_basedir="$HOME/.bin"
|
||||
local _cli_postfix=""
|
||||
case "$(uname -s)" in
|
||||
Linux)
|
||||
[ X"$(uname -m)" = X"x86_64" ] && _cli_postfix="-linux"
|
||||
[ X"$(uname -m)" = X"amd64" ] && _cli_postfix="-linux"
|
||||
;;
|
||||
Darwin) _cli_postfix="-macosx" ;;
|
||||
esac
|
||||
if [ X"${_cli_postfix}" = X"" ]; then
|
||||
ci_sq_ensure_java || return 1
|
||||
fi
|
||||
if [ X"${SONAR_SCANNER_PATH}" != X"" ]; then
|
||||
if [ -e "${SONAR_SCANNER_PATH}" ]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
local _cli_fname="sonar-scanner-cli-${_cli_version}${_cli_postfix}"
|
||||
[ ${CI_VERBOSE} -gt 0 ] && echo "[ci] ensure scanner ${_cli_fname}"
|
||||
local _cli_dname="sonar-scanner-${_cli_version}${_cli_postfix}"
|
||||
local _cli_archive="${_cli_basedir}/${_cli_fname}.zip"
|
||||
local _cli_dir="${_cli_basedir}/${_cli_dname}"
|
||||
local _cli_url="https://sonarsource.bintray.com/Distribution/sonar-scanner-cli/${_cli_fname}.zip"
|
||||
if [ ! -e "${_cli_archive}" ]; then
|
||||
mkdir -p -- "${_cli_basedir}" > /dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
ci_err_msg "could not create ${_cli_basedir}"
|
||||
return 1
|
||||
fi
|
||||
[ ${CI_VERBOSE} -gt 0 ] && echo "[ci] downloading ${_cli_fname}"
|
||||
curl -kL -o "${_cli_archive}" "${_cli_url}"
|
||||
[ $? -ne 0 ] && ci_err_msg "download failed" && return 1
|
||||
[ ! -e "${_cli_archive}" ] && ci_err_msg "download verify" && return 1
|
||||
fi
|
||||
if [ ! -d "${_cli_dir}" ]; then
|
||||
[ ${CI_VERBOSE} -gt 0 ] && echo "[ci] extracting ${_cli_fname}"
|
||||
unzip -od "${_cli_basedir}" "${_cli_archive}"
|
||||
[ $? -ne 0 ] && ci_err_msg "extract failed" && return 1
|
||||
[ ! -d "${_cli_dir}" ] && ci_err_msg "extract verify" && return 1
|
||||
fi
|
||||
if [ ! -e "${_cli_dir}/bin/sonar-scanner" ]; then
|
||||
ci_err_msg "sonar-scanner binary not found."
|
||||
return 1
|
||||
fi
|
||||
SONAR_SCANNER_PATH="${_cli_dir}/bin/sonar-scanner"
|
||||
return 0
|
||||
}
|
||||
|
||||
ci_sq_run() {
|
||||
if [ X"${SONAR_SCANNER_PATH}" = X"" ]; then
|
||||
ci_err_msg "environment variable SONAR_SCANNER_PATH not set"
|
||||
return 1
|
||||
fi
|
||||
if [ X"${SONAR_HOST_URL}" = X"" ]; then
|
||||
ci_err_msg "environment variable SONAR_HOST_URL not set"
|
||||
return 1
|
||||
fi
|
||||
if [ X"${SONAR_AUTH_TOKEN}" = X"" ]; then
|
||||
ci_err_msg "environment variable SONAR_AUTH_TOKEN not set"
|
||||
return 1
|
||||
fi
|
||||
local _pdir=$(ci_get_filedir "ssh-audit.py")
|
||||
if [ -z "${_pdir}" ]; then
|
||||
ci_err_msg "failed to find project directory"
|
||||
return 1
|
||||
fi
|
||||
local _odir=$(pwd)
|
||||
cd -- "${_pdir}"
|
||||
local _branch=$(git name-rev --name-only HEAD | cut -d '~' -f 1)
|
||||
case "${_branch}" in
|
||||
master) ;;
|
||||
develop) ;;
|
||||
*) ci_err_msg "unknown branch: ${_branch}"; return 1 ;;
|
||||
esac
|
||||
local _junit=$(cd -- "${_pdir}" && ls -1 reports/junit.*.xml | sort -r | head -1)
|
||||
if [ X"${_junit}" = X"" ]; then
|
||||
ci_err_msg "no junit.xml found"
|
||||
return 1
|
||||
fi
|
||||
local _project_ver=$(grep VERSION ssh-audit.py | head -1 | cut -d "'" -f 2)
|
||||
if [ -z "${_project_ver}" ]; then
|
||||
ci_err_msg "failed to get project version"
|
||||
return 1
|
||||
fi
|
||||
if [ -z "${_project_ver##*dev}" ]; then
|
||||
local _git_commit=$(git rev-parse --short=8 HEAD)
|
||||
_project_ver="${_project_ver}.${_git_commit}"
|
||||
fi
|
||||
[ ${CI_VERBOSE} -gt 0 ] && echo "[ci] run sonar-scanner for ${_project_ver}"
|
||||
"${SONAR_SCANNER_PATH}" -X \
|
||||
-Dsonar.projectKey=arthepsy-github:ssh-audit \
|
||||
-Dsonar.sources=ssh-audit.py \
|
||||
-Dsonar.tests=test \
|
||||
-Dsonar.test.inclusions=test/*.py \
|
||||
-Dsonar.host.url="${SONAR_HOST_URL}" \
|
||||
-Dsonar.projectName=ssh-audit \
|
||||
-Dsonar.projectVersion="${_project_ver}" \
|
||||
-Dsonar.branch="${_branch}" \
|
||||
-Dsonar.python.coverage.overallReportPath=reports/coverage.xml \
|
||||
-Dsonar.python.xunit.reportPath="${_junit}" \
|
||||
-Dsonar.organization=arthepsy-github \
|
||||
-Dsonar.login="${SONAR_AUTH_TOKEN}"
|
||||
cd -- "${_odir}"
|
||||
return 0
|
||||
}
|
||||
|
||||
ci_run_wrapped() {
|
||||
local _versions=$(echo "${PY_VER}" | sed -e 's/,/ /g')
|
||||
[ -z "${_versions}" ] && eval "$1"
|
||||
for _i in ${_versions}; do
|
||||
local _v=$(echo "$_i" | cut -d '/' -f 1)
|
||||
local _o=$(echo "$_i" | cut -d '/' -sf 2)
|
||||
[ -z "${_o}" ] && _o="${PY_ORIGIN}"
|
||||
eval "$1" "${_v}" "${_o}" || return 1
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
ci_step_before_install_wrapped() {
|
||||
local _py_ver="$1"
|
||||
local _py_ori="$2"
|
||||
case "${_py_ori}" in
|
||||
pyenv)
|
||||
if [ "${CI_PYENV_SETUP}" -eq 0 ]; then
|
||||
ci_pyenv_setup
|
||||
CI_PYENV_SETUP=1
|
||||
fi
|
||||
ci_pyenv_install "${_py_ver}" || return 1
|
||||
ci_pyenv_use "${_py_ver}" || return 1
|
||||
;;
|
||||
esac
|
||||
ci_pip_setup "${_py_ver}" || return 1
|
||||
ci_venv_setup "${_py_ver}" || return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
ci_step_before_install() {
|
||||
if ci_is_osx; then
|
||||
[ ${CI_VERBOSE} -gt 0 ] && sw_vers
|
||||
brew update || brew update
|
||||
brew install autoconf pkg-config openssl readline xz
|
||||
brew upgrade autoconf pkg-config openssl readline xz
|
||||
PY_ORIGIN=pyenv
|
||||
fi
|
||||
CI_PYENV_SETUP=0
|
||||
ci_run_wrapped "ci_step_before_install_wrapped" || return 1
|
||||
if [ "${CI_PYENV_SETUP}" -eq 1 ]; then
|
||||
pyenv shell --unset
|
||||
[ ${CI_VERBOSE} -gt 0 ] && pyenv versions
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
ci_step_install_wrapped() {
|
||||
local _py_ver="$1"
|
||||
ci_venv_use "${_py_ver}"
|
||||
pip install -U tox coveralls codecov
|
||||
ci_err $? "failed to install dependencies" || return 0
|
||||
}
|
||||
|
||||
ci_step_script_wrapped() {
|
||||
local _py_ver="$1"
|
||||
local _py_ori="$2"
|
||||
local _py_env=$(ci_get_py_env "${_py_ver}")
|
||||
ci_venv_use "${_py_ver}" || return 1
|
||||
if [ -z "${_py_env##*py3*}" ]; then
|
||||
if [ -z "${_py_env##*pypy3*}" ]; then
|
||||
# NOTE: workaround for travis environment
|
||||
_pydir=$(dirname $(which python))
|
||||
ln -s -- "${_pydir}/python" "${_pydir}/pypy3"
|
||||
# NOTE: do not lint, as it hangs when flake8 is run
|
||||
# NOTE: do not type, as it can't install dependencies
|
||||
TOXENV=${_py_env}-test
|
||||
else
|
||||
TOXENV=${_py_env}-test,${_py_env}-type,${_py_env}-lint
|
||||
fi
|
||||
else
|
||||
# NOTE: do not type, as it isn't supported on py2x
|
||||
TOXENV=${_py_env}-test,${_py_env}-lint
|
||||
fi
|
||||
tox -e $TOXENV,cov
|
||||
ci_err $? "tox failed" || return 0
|
||||
}
|
||||
|
||||
ci_step_success_wrapped() {
|
||||
local _py_ver="$1"
|
||||
local _py_ori="$2"
|
||||
if [ X"${SQ}" = X"1" ]; then
|
||||
ci_sq_ensure_scanner && ci_sq_run
|
||||
fi
|
||||
ci_venv_use "${_py_ver}" || return 1
|
||||
coveralls
|
||||
codecov
|
||||
}
|
||||
|
||||
ci_step_failure() {
|
||||
cat .tox/log/*
|
||||
cat .tox/*/log/*
|
||||
}
|
||||
|
||||
ci_step_install() { ci_run_wrapped "ci_step_install_wrapped"; }
|
||||
ci_step_script() { ci_run_wrapped "ci_step_script_wrapped"; }
|
||||
ci_step_success() { ci_run_wrapped "ci_step_success_wrapped"; }
|
131
test/tools/ci-win.cmd
Normal file
131
test/tools/ci-win.cmd
Normal file
@ -0,0 +1,131 @@
|
||||
@ECHO OFF
|
||||
|
||||
IF "%PYTHON%" == "" (
|
||||
ECHO PYTHON environment variable not set
|
||||
EXIT 1
|
||||
)
|
||||
SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
|
||||
FOR /F %%i IN ('python -c "import platform; print(platform.python_version());"') DO (
|
||||
SET PYTHON_VERSION=%%i
|
||||
)
|
||||
SET PYTHON_VERSION_MAJOR=%PYTHON_VERSION:~0,1%
|
||||
IF "%PYTHON_VERSION:~3,1%" == "." (
|
||||
SET PYTHON_VERSION_MINOR=%PYTHON_VERSION:~2,1%
|
||||
) ELSE (
|
||||
SET PYTHON_VERSION_MINOR=%PYTHON_VERSION:~2,2%
|
||||
)
|
||||
FOR /F %%i IN ('python -c "import struct; print(struct.calcsize(\"P\")*8)"') DO (
|
||||
SET PYTHON_ARCH=%%i
|
||||
)
|
||||
CALL :devenv
|
||||
|
||||
IF /I "%1"=="" (
|
||||
SET target=test
|
||||
) ELSE (
|
||||
SET target=%1
|
||||
)
|
||||
|
||||
echo [CI] TARGET=%target%
|
||||
GOTO %target%
|
||||
|
||||
:devenv
|
||||
SET WIN_SDK_ROOT=C:\Program Files\Microsoft SDKs\Windows
|
||||
SET VS2015_ROOT=C:\Program Files (x86)\Microsoft Visual Studio 14.0
|
||||
IF %PYTHON_VERSION_MAJOR% == 2 (
|
||||
SET WINDOWS_SDK_VERSION="v7.0"
|
||||
) ELSE IF %PYTHON_VERSION_MAJOR% == 3 (
|
||||
IF %PYTHON_VERSION_MAJOR% LEQ 4 (
|
||||
SET WINDOWS_SDK_VERSION="v7.1"
|
||||
) ELSE (
|
||||
SET WINDOWS_SDK_VERSION="2015"
|
||||
)
|
||||
) ELSE (
|
||||
ECHO Unsupported Python version: "%PYTHON_VERSION%"
|
||||
EXIT 1
|
||||
)
|
||||
SETLOCAL ENABLEDELAYEDEXPANSION
|
||||
IF %PYTHON_ARCH% == 32 (SET PYTHON_ARCHX=x86) ELSE (SET PYTHON_ARCHX=x64)
|
||||
IF %WINDOWS_SDK_VERSION% == "2015" (
|
||||
"%VS2015_ROOT%\VC\vcvarsall.bat" %PYTHON_ARCHX%
|
||||
) ELSE (
|
||||
SET DISTUTILS_USE_SDK=1
|
||||
SET MSSdk=1
|
||||
"%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Setup\WindowsSdkVer.exe" -q -version:%WINDOWS_SDK_VERSION%
|
||||
"%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Bin\SetEnv.cmd" /%PYTHON_ARCHX% /release
|
||||
)
|
||||
GOTO :eof
|
||||
|
||||
:install
|
||||
pip install --user --upgrade pip virtualenv
|
||||
SET VENV_DIR=.venv\%PYTHON_VERSION%
|
||||
rmdir /s /q %VENV_DIR% > nul 2>nul
|
||||
mkdir .venv > nul 2>nul
|
||||
IF "%PYTHON_VERSION_MAJOR%%PYTHON_VERSION_MINOR%" == "26" (
|
||||
python -c "import virtualenv; virtualenv.main();" %VENV_DIR%
|
||||
) ELSE (
|
||||
python -m virtualenv %VENV_DIR%
|
||||
)
|
||||
CALL %VENV_DIR%\Scripts\activate
|
||||
python -V
|
||||
pip install tox
|
||||
deactivate
|
||||
GOTO :eof
|
||||
|
||||
:install_deps
|
||||
SET LXML_FILE=
|
||||
SET LXML_URL=
|
||||
IF %PYTHON_VERSION_MAJOR% == 3 (
|
||||
IF %PYTHON_VERSION_MINOR% == 3 (
|
||||
IF %PYTHON_ARCH% == 32 (
|
||||
SET LXML_FILE=lxml-3.7.3.win32-py3.3.exe
|
||||
SET LXML_URL=https://pypi.python.org/packages/66/fd/b82a54e7a15e91184efeef4b659379d0581a73cf78239d70feb0f0877841/lxml-3.7.3.win32-py3.3.exe
|
||||
) ELSE (
|
||||
SET LXML_FILE=lxml-3.7.3.win-amd64-py3.3.exe
|
||||
SET LXML_URL=https://pypi.python.org/packages/dc/bc/4742b84793fa1fd991b5d2c6f2e5d32695659d6cfedf5c66aef9274a8723/lxml-3.7.3.win-amd64-py3.3.exe
|
||||
)
|
||||
) ELSE IF %PYTHON_VERSION_MINOR% == 4 (
|
||||
IF %PYTHON_ARCH% == 32 (
|
||||
SET LXML_FILE=lxml-3.7.3.win32-py3.4.exe
|
||||
SET LXML_URL=https://pypi.python.org/packages/88/33/265459d68d465ddc707621e6471989f5c2cb0d43f230f516800ffd629af7/lxml-3.7.3.win32-py3.4.exe
|
||||
) ELSE (
|
||||
SET LXML_FILE=lxml-3.7.3.win-amd64-py3.4.exe
|
||||
SET LXML_URL=https://pypi.python.org/packages/2d/65/e47db7f36a69a1b59b4f661e42d699d6c43e663b8fd91035e6f7681d017e/lxml-3.7.3.win-amd64-py3.4.exe
|
||||
)
|
||||
)
|
||||
)
|
||||
IF NOT "%LXML_FILE%" == "" (
|
||||
CALL :download %LXML_URL% .downloads\%LXML_FILE%
|
||||
easy_install --user .downloads\%LXML_FILE%
|
||||
)
|
||||
GOTO :eof
|
||||
|
||||
:test
|
||||
SET VENV_DIR=.venv\%PYTHON_VERSION%
|
||||
CALL %VENV_DIR%\Scripts\activate
|
||||
IF "%TOXENV%" == "" (
|
||||
SET TOXENV=py%PYTHON_VERSION_MAJOR%%PYTHON_VERSION_MINOR%
|
||||
)
|
||||
IF "%PYTHON_VERSION_MAJOR%%PYTHON_VERSION_MINOR%" == "26" (
|
||||
SET TOX=python -c "from tox import cmdline; cmdline()"
|
||||
) ELSE (
|
||||
SET TOX=python -m tox
|
||||
)
|
||||
IF %PYTHON_VERSION_MAJOR% == 3 (
|
||||
IF %PYTHON_VERSION_MINOR% LEQ 4 (
|
||||
:: Python 3.3 and 3.4 does not support typed-ast (mypy dependency)
|
||||
%TOX% --sitepackages -e %TOXENV%-test,%TOXENV%-lint,cov || EXIT 1
|
||||
) ELSE (
|
||||
%TOX% --sitepackages -e %TOXENV%-test,%TOXENV%-type,%TOXENV%-lint,cov || EXIT 1
|
||||
)
|
||||
) ELSE (
|
||||
%TOX% --sitepackages -e %TOXENV%-test,%TOXENV%-lint,cov || EXIT 1
|
||||
)
|
||||
GOTO :eof
|
||||
|
||||
:download
|
||||
IF NOT EXIST %2 (
|
||||
IF NOT EXIST .downloads\ mkdir .downloads
|
||||
powershell -command "(new-object net.webclient).DownloadFile('%1', '%2')" || EXIT 1
|
||||
|
||||
)
|
||||
GOTO :eof
|
158
tox.ini
Normal file
158
tox.ini
Normal file
@ -0,0 +1,158 @@
|
||||
[tox]
|
||||
envlist =
|
||||
py26-{test,vulture}
|
||||
py{27,py,py3}-{test,pylint,flake8,vulture}
|
||||
py{33,34,35,36,37}-{test,mypy,pylint,flake8,vulture}
|
||||
cov
|
||||
skipsdist = true
|
||||
skip_missing_interpreters = true
|
||||
|
||||
[testenv]
|
||||
deps =
|
||||
test: pytest==3.0.7
|
||||
test,cov: {[testenv:cov]deps}
|
||||
test,py{33,34,35,36,37}-{type,mypy}: colorama==0.3.7
|
||||
py{33,34,35,36,37}-{type,mypy}: {[testenv:mypy]deps}
|
||||
py{27,py,py3,33,34,35,36,37}-{lint,pylint},lint: {[testenv:pylint]deps}
|
||||
py{27,py,py3,33,34,35,36,37}-{lint,flake8},lint: {[testenv:flake8]deps}
|
||||
py{27,py,py3,33,34,35,36,37}-{lint,vulture},lint: {[testenv:vulture]deps}
|
||||
setenv =
|
||||
SSHAUDIT = {toxinidir}/ssh-audit.py
|
||||
test: COVERAGE_FILE = {toxinidir}/.coverage.{envname}
|
||||
type,mypy: MYPYPATH = {toxinidir}/test/stubs
|
||||
type,mypy: MYPYHTML = {toxinidir}/reports/html/mypy
|
||||
commands =
|
||||
test: coverage run --source ssh-audit -m -- \
|
||||
test: pytest -v --junitxml={toxinidir}/reports/junit.{envname}.xml {posargs:test}
|
||||
test: coverage report --show-missing
|
||||
test: coverage html -d {toxinidir}/reports/html/coverage.{envname}
|
||||
py{33,34,35,36,37}-{type,mypy}: {[testenv:mypy]commands}
|
||||
py{27,py,py3,33,34,35,36,37}-{lint,pylint},lint: {[testenv:pylint]commands}
|
||||
py{27,py,py3,33,34,35,36,37}-{lint,flake8},lint: {[testenv:flake8]commands}
|
||||
py{27,py,py3,33,34,35,36,37}-{lint,vulture},lint: {[testenv:vulture]commands}
|
||||
ignore_outcome =
|
||||
type: true
|
||||
lint: true
|
||||
|
||||
[testenv:cov]
|
||||
deps =
|
||||
coverage==4.3.4
|
||||
setenv =
|
||||
COVERAGE_FILE = {toxinidir}/.coverage
|
||||
commands =
|
||||
coverage erase
|
||||
coverage combine
|
||||
coverage report --show-missing
|
||||
coverage xml -i -o {toxinidir}/reports/coverage.xml
|
||||
coverage html -d {toxinidir}/reports/html/coverage
|
||||
|
||||
[testenv:mypy]
|
||||
deps =
|
||||
colorama==0.3.7
|
||||
lxml==3.7.3
|
||||
mypy==0.501
|
||||
commands =
|
||||
mypy \
|
||||
--show-error-context \
|
||||
--config-file {toxinidir}/tox.ini \
|
||||
--html-report {env:MYPYHTML}.py3.{envname} \
|
||||
{posargs:{env:SSHAUDIT}}
|
||||
mypy \
|
||||
-2 \
|
||||
--no-warn-incomplete-stub \
|
||||
--show-error-context \
|
||||
--config-file {toxinidir}/tox.ini \
|
||||
--html-report {env:MYPYHTML}.py2.{envname} \
|
||||
{posargs:{env:SSHAUDIT}}
|
||||
|
||||
[testenv:pylint]
|
||||
deps =
|
||||
mccabe
|
||||
pylint
|
||||
commands =
|
||||
pylint \
|
||||
--rcfile tox.ini \
|
||||
--load-plugins=pylint.extensions.bad_builtin \
|
||||
--load-plugins=pylint.extensions.check_elif \
|
||||
--load-plugins=pylint.extensions.mccabe \
|
||||
{posargs:{env:SSHAUDIT}}
|
||||
|
||||
[testenv:flake8]
|
||||
deps =
|
||||
flake8
|
||||
commands =
|
||||
flake8 {posargs:{env:SSHAUDIT}}
|
||||
|
||||
[testenv:vulture]
|
||||
deps =
|
||||
vulture
|
||||
commands =
|
||||
python -c "import sys; from subprocess import Popen, PIPE; \
|
||||
a = ['vulture'] + r'{posargs:{env:SSHAUDIT}}'.split(' '); \
|
||||
o = Popen(a, shell=False, stdout=PIPE).communicate()[0]; \
|
||||
l = [x for x in o.split(b'\n') if x and b'Unused import' not in x]; \
|
||||
print(b'\n'.join(l).decode('utf-8')); \
|
||||
sys.exit(1 if len(l) > 0 else 0)"
|
||||
|
||||
|
||||
[mypy]
|
||||
ignore_missing_imports = False
|
||||
follow_imports = error
|
||||
disallow_untyped_calls = True
|
||||
disallow_untyped_defs = True
|
||||
check_untyped_defs = True
|
||||
disallow_subclassing_any = True
|
||||
warn_incomplete_stub = True
|
||||
warn_redundant_casts = True
|
||||
warn_return_any = True
|
||||
warn_unused_ignores = True
|
||||
strict_optional = True
|
||||
strict_boolean = True
|
||||
|
||||
[pylint]
|
||||
reports = no
|
||||
#output-format = colorized
|
||||
indent-string = \t
|
||||
disable =
|
||||
locally-disabled,
|
||||
bad-continuation,
|
||||
multiple-imports,
|
||||
invalid-name,
|
||||
trailing-whitespace,
|
||||
missing-docstring
|
||||
max-complexity = 15
|
||||
max-args = 8
|
||||
max-locals = 20
|
||||
max-returns = 6
|
||||
max-branches = 15
|
||||
max-statements = 60
|
||||
max-parents = 7
|
||||
max-attributes = 8
|
||||
min-public-methods = 1
|
||||
max-public-methods = 20
|
||||
max-bool-expr = 5
|
||||
max-nested-blocks = 6
|
||||
max-line-length = 80
|
||||
ignore-long-lines = ^\s*(#\s+type:\s+.*|[A-Z0-9_]+\s+=\s+.*|('.*':\s+)?\[.*\],?|assert\s+.*)$
|
||||
max-module-lines = 2500
|
||||
|
||||
[flake8]
|
||||
ignore =
|
||||
# indentation contains tabs
|
||||
W191,
|
||||
# blank line contains whitespace
|
||||
W293,
|
||||
# indentation contains mixed spaces and tabs
|
||||
E101,
|
||||
# multiple spaces before operator
|
||||
E221,
|
||||
# multiple spaces after operator
|
||||
E241,
|
||||
# multiple imports on one line
|
||||
E401,
|
||||
# line too long
|
||||
E501,
|
||||
# module imported but unused
|
||||
F401,
|
||||
# undefined name
|
||||
F821
|
Loading…
Reference in New Issue
Block a user