mirror of https://github.com/CIRCL/PyCIRCLean
Reorganize tests making them easier to run
- The tests now automatically run depending on whether you have the dependencies installed, instead of failing and throwing exceptions. - CONTRIBUTING.md has more information on how to run the tests. - When the tests run, they will save their logs to /test_logs instead of printing them so you can read them later. - Change names of source file directories to make them more descriptivepull/8/head
parent
70a73dc292
commit
3dad4faa61
10
.travis.yml
10
.travis.yml
|
@ -61,7 +61,7 @@ install:
|
||||||
- pushd theZoo/malwares/Binaries
|
- pushd theZoo/malwares/Binaries
|
||||||
- python unpackall.py
|
- python unpackall.py
|
||||||
- popd
|
- popd
|
||||||
- mv theZoo/malwares/Binaries/out tests/src_complex/
|
- mv theZoo/malwares/Binaries/out tests/src_invalid/
|
||||||
# Path traversal
|
# Path traversal
|
||||||
- git clone https://github.com/jwilk/path-traversal-samples
|
- git clone https://github.com/jwilk/path-traversal-samples
|
||||||
- pushd path-traversal-samples
|
- pushd path-traversal-samples
|
||||||
|
@ -72,12 +72,12 @@ install:
|
||||||
- make
|
- make
|
||||||
- popd
|
- popd
|
||||||
- popd
|
- popd
|
||||||
- mv path-traversal-samples/zip/*.zip tests/src_complex/
|
- mv path-traversal-samples/zip/*.zip tests/src_invalid/
|
||||||
- mv path-traversal-samples/rar/*.rar tests/src_complex/
|
- mv path-traversal-samples/rar/*.rar tests/src_invalid/
|
||||||
# Office docs
|
# Office docs
|
||||||
- git clone https://github.com/eea/odfpy.git
|
- git clone https://github.com/eea/odfpy.git
|
||||||
- mv odfpy/tests/examples/* tests/src_complex/
|
- mv odfpy/tests/examples/* tests/src_invalid/
|
||||||
- pushd tests/src_complex/
|
- pushd tests/src_invalid/
|
||||||
- wget https://bitbucket.org/decalage/olefileio_pl/raw/3073963b640935134ed0da34906fea8e506460be/Tests/images/test-ole-file.doc
|
- wget https://bitbucket.org/decalage/olefileio_pl/raw/3073963b640935134ed0da34906fea8e506460be/Tests/images/test-ole-file.doc
|
||||||
- wget --no-check-certificate https://www.officedissector.com/corpus/fraunhoferlibrary.zip
|
- wget --no-check-certificate https://www.officedissector.com/corpus/fraunhoferlibrary.zip
|
||||||
- unzip -o fraunhoferlibrary.zip
|
- unzip -o fraunhoferlibrary.zip
|
||||||
|
|
|
@ -29,5 +29,13 @@ or if you have an example you'd like to contribute.
|
||||||
Running the tests
|
Running the tests
|
||||||
=================
|
=================
|
||||||
|
|
||||||
* Running the tests is easy. First, make sure you've installed the project and testing dependencies.
|
* Running the tests is fairly straightforward.
|
||||||
Then, run `python -m pytest` or just `pytest` in the top level or /tests directory.
|
* First, make sure you've installed the project and testing dependencies.
|
||||||
|
* Then, run `python -m pytest` or just `pytest` in the top level directory of the module.
|
||||||
|
* Each integration test that runs will generate a timestamped copy of the log for that run
|
||||||
|
in the tests/testlogs directory.
|
||||||
|
* If you'd like to get information about code coverage, run the tests using
|
||||||
|
`pytest --cov=kittengroomer`.
|
||||||
|
* You can test with multiple versions of Python if you have them installed
|
||||||
|
by running `pip install tox` and then `tox`. Make sure you modify "envlist"
|
||||||
|
in tox.ini for the Python versions you plan to use.
|
||||||
|
|
|
@ -15,6 +15,10 @@ Requirements per script
|
||||||
filecheck.py
|
filecheck.py
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
This is the script used by the [CIRCLean](https://github.com/CIRCL/Circlean)
|
||||||
|
USB key sanitizer. It is designed to handle a range of file types, and will
|
||||||
|
mark them as dangerous if they meet certain criteria.
|
||||||
|
|
||||||
Requirements by type of document:
|
Requirements by type of document:
|
||||||
* Microsoft office: oletools, olefile
|
* Microsoft office: oletools, olefile
|
||||||
* OOXML: officedissector
|
* OOXML: officedissector
|
||||||
|
@ -23,12 +27,14 @@ Requirements by type of document:
|
||||||
* Metadata: exifread
|
* Metadata: exifread
|
||||||
* Images: pillow
|
* Images: pillow
|
||||||
|
|
||||||
|
Note: pdfid is a not installable with pip. It must be downloaded and installed
|
||||||
|
manually in the directory where filecheck will be run.
|
||||||
|
|
||||||
```
|
```
|
||||||
sudo apt-get install p7zip-full p7zip-rar libxml2-dev libxslt1-dev
|
sudo apt-get install p7zip-full p7zip-rar libxml2-dev libxslt1-dev
|
||||||
pip install lxml oletools olefile pillow exifread
|
pip install lxml oletools olefile pillow exifread
|
||||||
pip install git+https://github.com/Rafiot/officedissector.git
|
pip install git+https://github.com/Rafiot/officedissector.git
|
||||||
# pdfid is not a package, installing manually
|
# installing pdfid manually
|
||||||
wget https://didierstevens.com/files/software/pdfid_v0_2_1.zip
|
wget https://didierstevens.com/files/software/pdfid_v0_2_1.zip
|
||||||
unzip pdfid_v0_2_1.zip
|
unzip pdfid_v0_2_1.zip
|
||||||
```
|
```
|
||||||
|
@ -36,6 +42,9 @@ Requirements by type of document:
|
||||||
generic.py
|
generic.py
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
This is a script used by an older version of CIRCLean. It has more dependencies
|
||||||
|
than filecheck.py and they are more complicated to install.
|
||||||
|
|
||||||
Requirements by type of document:
|
Requirements by type of document:
|
||||||
* Office and all text files: unoconv, libreoffice
|
* Office and all text files: unoconv, libreoffice
|
||||||
* PDF: ghostscript, pdf2htmlEX
|
* PDF: ghostscript, pdf2htmlEX
|
||||||
|
@ -60,9 +69,16 @@ Requirements by type of document:
|
||||||
pier9.py
|
pier9.py
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
This script has a list of file formats for various brands of industrial
|
||||||
|
manufacturing equipment, such as 3d printers, CNC machines, etc. It only
|
||||||
|
copies files that match these file formats.
|
||||||
|
|
||||||
No external dependencies required.
|
No external dependencies required.
|
||||||
|
|
||||||
specific.py
|
specific.py
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
As the name suggests, this script copies only specific file formats according
|
||||||
|
to the configuration provided by the user.
|
||||||
|
|
||||||
No external dependencies required.
|
No external dependencies required.
|
||||||
|
|
|
@ -197,6 +197,7 @@ class KittenGroomerFileCheck(KittenGroomerBase):
|
||||||
|
|
||||||
def _print_log(self):
|
def _print_log(self):
|
||||||
"""Print the logs related to the current file being processed."""
|
"""Print the logs related to the current file being processed."""
|
||||||
|
# TODO: change name to _write_log
|
||||||
tmp_log = self.log_name.fields(**self.cur_file.log_details)
|
tmp_log = self.log_name.fields(**self.cur_file.log_details)
|
||||||
if self.cur_file.is_dangerous():
|
if self.cur_file.is_dangerous():
|
||||||
tmp_log.warning(self.cur_file.log_string)
|
tmp_log.warning(self.cur_file.log_string)
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def save_logs(groomer, test_description):
|
||||||
|
divider = ('=' * 10 + '{}' + '=' * 10 + '\n')
|
||||||
|
test_log_path = 'tests/test_logs/{}.log'.format(test_description)
|
||||||
|
with open(test_log_path, 'w+') as test_log:
|
||||||
|
test_log.write(divider.format('TEST LOG'))
|
||||||
|
with open(groomer.log_processing, 'r') as logfile:
|
||||||
|
log = logfile.read()
|
||||||
|
test_log.write(log)
|
||||||
|
if groomer.debug:
|
||||||
|
if os.path.exists(groomer.log_debug_err):
|
||||||
|
test_log.write(divider.format('ERR LOG'))
|
||||||
|
with open(groomer.log_debug_err, 'r') as debug_err:
|
||||||
|
err = debug_err.read()
|
||||||
|
test_log.write(err)
|
||||||
|
if os.path.exists(groomer.log_debug_out):
|
||||||
|
test_log.write(divider.format('OUT LOG'))
|
||||||
|
with open(groomer.log_debug_out, 'r') as debug_out:
|
||||||
|
out = debug_out.read()
|
||||||
|
test_log.write(out)
|
Binary file not shown.
Binary file not shown.
|
@ -1,84 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from bin.specific import KittenGroomerSpec
|
|
||||||
from bin.pier9 import KittenGroomerPier9
|
|
||||||
from bin.generic import KittenGroomer
|
|
||||||
from bin.filecheck import KittenGroomerFileCheck
|
|
||||||
|
|
||||||
|
|
||||||
skip = pytest.mark.skip
|
|
||||||
py2_only = pytest.mark.skipif(sys.version_info.major == 3,
|
|
||||||
reason="filecheck.py only runs on python 2")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def src_simple():
|
|
||||||
return os.path.join(os.getcwd(), 'tests/src_simple')
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def src_complex():
|
|
||||||
return os.path.join(os.getcwd(), 'tests/src_complex')
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def dst():
|
|
||||||
return os.path.join(os.getcwd(), 'tests/dst')
|
|
||||||
|
|
||||||
|
|
||||||
def test_specific_valid(src_simple, dst):
|
|
||||||
spec = KittenGroomerSpec(src_simple, dst, debug=True)
|
|
||||||
spec.processdir()
|
|
||||||
dump_logs(spec)
|
|
||||||
|
|
||||||
|
|
||||||
def test_specific_invalid(src_complex, dst):
|
|
||||||
spec = KittenGroomerSpec(src_complex, dst, debug=True)
|
|
||||||
spec.processdir()
|
|
||||||
dump_logs(spec)
|
|
||||||
|
|
||||||
|
|
||||||
def test_pier9(src_complex, dst):
|
|
||||||
spec = KittenGroomerPier9(src_complex, dst, debug=True)
|
|
||||||
spec.processdir()
|
|
||||||
dump_logs(spec)
|
|
||||||
|
|
||||||
|
|
||||||
def test_generic(src_simple, dst):
|
|
||||||
spec = KittenGroomer(src_simple, dst, debug=True)
|
|
||||||
spec.processdir()
|
|
||||||
dump_logs(spec)
|
|
||||||
|
|
||||||
|
|
||||||
def test_generic_2(src_complex, dst):
|
|
||||||
spec = KittenGroomer(src_complex, dst, debug=True)
|
|
||||||
spec.processdir()
|
|
||||||
dump_logs(spec)
|
|
||||||
|
|
||||||
|
|
||||||
def test_filecheck(src_complex, dst):
|
|
||||||
spec = KittenGroomerFileCheck(src_complex, dst, debug=True)
|
|
||||||
spec.processdir()
|
|
||||||
dump_logs(spec)
|
|
||||||
|
|
||||||
|
|
||||||
def test_filecheck_2(src_simple, dst):
|
|
||||||
spec = KittenGroomerFileCheck(src_simple, dst, debug=True)
|
|
||||||
spec.processdir()
|
|
||||||
dump_logs(spec)
|
|
||||||
|
|
||||||
## Helper functions
|
|
||||||
|
|
||||||
def dump_logs(spec):
|
|
||||||
print(open(spec.log_processing, 'rb').read())
|
|
||||||
if spec.debug:
|
|
||||||
if os.path.exists(spec.log_debug_err):
|
|
||||||
print(open(spec.log_debug_err, 'rb').read())
|
|
||||||
if os.path.exists(spec.log_debug_out):
|
|
||||||
print(open(spec.log_debug_out, 'rb').read())
|
|
|
@ -1,16 +1,48 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from tests.logging import save_logs
|
||||||
|
try:
|
||||||
from bin.filecheck import KittenGroomerFileCheck, File, main
|
from bin.filecheck import KittenGroomerFileCheck, File, main
|
||||||
|
NODEPS = False
|
||||||
|
except ImportError:
|
||||||
|
NODEPS = True
|
||||||
|
|
||||||
|
skipif_nodeps = pytest.mark.skipif(NODEPS,
|
||||||
|
reason="Dependencies aren't installed")
|
||||||
|
|
||||||
|
|
||||||
|
@skipif_nodeps
|
||||||
|
class TestIntegration:
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def src_valid(self):
|
||||||
|
return os.path.join(os.getcwd(), 'tests/src_valid')
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def src_invalid(self):
|
||||||
|
return os.path.join(os.getcwd(), 'tests/src_invalid')
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def dst(self):
|
||||||
|
return os.path.join(os.getcwd(), 'tests/dst')
|
||||||
|
|
||||||
|
def test_filecheck(self, src_invalid, dst):
|
||||||
|
groomer = KittenGroomerFileCheck(src_invalid, dst, debug=True)
|
||||||
|
groomer.processdir()
|
||||||
|
test_description = "filecheck_invalid"
|
||||||
|
save_logs(groomer, test_description)
|
||||||
|
|
||||||
|
def test_filecheck_2(self, src_valid, dst):
|
||||||
|
groomer = KittenGroomerFileCheck(src_valid, dst, debug=True)
|
||||||
|
groomer.processdir()
|
||||||
|
test_description = "filecheck_valid"
|
||||||
|
save_logs(groomer, test_description)
|
||||||
|
|
||||||
|
|
||||||
class TestFileHandling:
|
class TestFileHandling:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# We're going to give KittenGroomer a bunch of files, and it's going to process them
|
|
||||||
# Maybe we want to make a function that processdir delegates to? Or is it just the File Object that's responsible?
|
|
||||||
# Ideally we should be able to pass a path to a function and have it do stuff? And then we can test that function?
|
|
||||||
# So we have a function that takes a path and returns...log info? That makes sense actually. Or some sort of meta data
|
|
||||||
# The function could maybe be called processfile
|
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from bin.generic import KittenGroomer, File, main
|
||||||
|
from tests.logging import save_logs
|
||||||
|
|
||||||
|
skipif_nodeps = pytest.mark.skipif(os.path.exists('/usr/bin/unoconv') is False,
|
||||||
|
reason="Dependencies aren't installed")
|
||||||
|
|
||||||
|
|
||||||
|
@skipif_nodeps
|
||||||
|
class TestIntegration:
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def src_valid(self):
|
||||||
|
return os.path.join(os.getcwd(), 'tests/src_valid')
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def src_invalid(self):
|
||||||
|
return os.path.join(os.getcwd(), 'tests/src_invalid')
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def dst(self):
|
||||||
|
return os.path.join(os.getcwd(), 'tests/dst')
|
||||||
|
|
||||||
|
def test_generic(self, src_valid, dst):
|
||||||
|
groomer = KittenGroomer(src_valid, dst, debug=True)
|
||||||
|
groomer.processdir()
|
||||||
|
test_description = 'generic_valid'
|
||||||
|
save_logs(groomer, test_description)
|
||||||
|
|
||||||
|
def test_generic_2(self, src_invalid, dst):
|
||||||
|
groomer = KittenGroomer(src_invalid, dst, debug=True)
|
||||||
|
groomer.processdir()
|
||||||
|
test_description = 'generic_invalid'
|
||||||
|
save_logs(groomer, test_description)
|
||||||
|
|
||||||
|
|
||||||
|
class TestFileHandling:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# We're going to give KittenGroomer a bunch of files, and it's going to process them
|
||||||
|
# Maybe we want to make a function that processdir delegates to? Or is it just the File Object that's responsible?
|
||||||
|
# Ideally we should be able to pass a path to a function and have it do stuff? And then we can test that function?
|
||||||
|
# So we have a function that takes a path and returns...log info? That makes sense actually. Or some sort of meta data
|
||||||
|
# The function could maybe be called processfile
|
|
@ -21,7 +21,7 @@ class TestFileBase:
|
||||||
|
|
||||||
@fixture
|
@fixture
|
||||||
def source_file(self):
|
def source_file(self):
|
||||||
return 'tests/src_simple/blah.conf'
|
return 'tests/src_valid/blah.conf'
|
||||||
|
|
||||||
@fixture
|
@fixture
|
||||||
def dest_file(self):
|
def dest_file(self):
|
||||||
|
@ -84,7 +84,7 @@ class TestFileBase:
|
||||||
# We should probably catch everytime that happens and tell the user explicitly happened (and maybe put it in the log)
|
# We should probably catch everytime that happens and tell the user explicitly happened (and maybe put it in the log)
|
||||||
|
|
||||||
def test_create(self):
|
def test_create(self):
|
||||||
file = FileBase('tests/src_simple/blah.conf', '/tests/dst/blah.conf')
|
file = FileBase('tests/src_valid/blah.conf', '/tests/dst/blah.conf')
|
||||||
|
|
||||||
def test_create_broken(self, tmpdir):
|
def test_create_broken(self, tmpdir):
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
|
@ -221,7 +221,7 @@ class TestKittenGroomerBase:
|
||||||
|
|
||||||
@fixture
|
@fixture
|
||||||
def source_directory(self):
|
def source_directory(self):
|
||||||
return 'tests/src_complex'
|
return 'tests/src_invalid'
|
||||||
|
|
||||||
@fixture
|
@fixture
|
||||||
def dest_directory(self):
|
def dest_directory(self):
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from bin.specific import KittenGroomerSpec
|
||||||
|
from bin.pier9 import KittenGroomerPier9
|
||||||
|
from tests.logging import save_logs
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def src_valid():
|
||||||
|
return os.path.join(os.getcwd(), 'tests/src_valid')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def src_invalid():
|
||||||
|
return os.path.join(os.getcwd(), 'tests/src_invalid')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def dst():
|
||||||
|
return os.path.join(os.getcwd(), 'tests/dst')
|
||||||
|
|
||||||
|
|
||||||
|
def test_specific_valid(src_valid, dst):
|
||||||
|
groomer = KittenGroomerSpec(src_valid, dst, debug=True)
|
||||||
|
groomer.processdir()
|
||||||
|
test_description = 'specific_valid'
|
||||||
|
save_logs(groomer, test_description)
|
||||||
|
|
||||||
|
|
||||||
|
def test_specific_invalid(src_invalid, dst):
|
||||||
|
groomer = KittenGroomerSpec(src_invalid, dst, debug=True)
|
||||||
|
groomer.processdir()
|
||||||
|
test_description = 'specific_invalid'
|
||||||
|
save_logs(groomer, test_description)
|
||||||
|
|
||||||
|
|
||||||
|
def test_pier9_valid(src_invalid, dst):
|
||||||
|
groomer = KittenGroomerPier9(src_invalid, dst, debug=True)
|
||||||
|
groomer.processdir()
|
||||||
|
test_description = 'pier9_valid'
|
||||||
|
save_logs(groomer, test_description)
|
||||||
|
|
||||||
|
|
||||||
|
def test_pier9_invalid(src_invalid, dst):
|
||||||
|
groomer = KittenGroomerPier9(src_invalid, dst, debug=True)
|
||||||
|
groomer.processdir()
|
||||||
|
test_description = 'pier9_invalid'
|
||||||
|
save_logs(groomer, test_description)
|
Loading…
Reference in New Issue