Adding unique_id to tests

Adding a simple counter based id to tests to track them in debug
log and results log. Also, this patch improves the result output
like adding a progress bar and uniform test names..

Change-Id: Ib83181b25a0c18c7993f491cde98d73555b01404
This commit is contained in:
Rahul Nair 2016-07-21 04:36:16 -05:00 committed by Rahul U Nair
parent fc1c207040
commit 0009bf49ce
17 changed files with 317 additions and 60 deletions

View File

@ -154,10 +154,12 @@ def list_cli_opts():
cfg.MultiStrOpt("excluded-types", dest="excluded_types", short="e",
default=[""],
help="Test types to be excluded from current run"
"against the target API"
),
"against the target API"),
cfg.BoolOpt("list-tests", dest="list_tests", short="L", default=False,
help="List all available test types that can be run"),
help="List all available test types that can be run"
" against the target API"),
cfg.BoolOpt("colorize", dest="colorize", short="cl", default=False,
help="Enable color in Syntribos terminal output"),
cfg.BoolOpt("dry-run", dest="dry_run", short="D", default=False,
help="Don't run tests, just print them out to console"),
cfg.StrOpt("outfile", short="o", default=None,

View File

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
SEP = "=" * 80
SEP = "=" * 120
RANKING = ['UNDEFINED', 'LOW', 'MEDIUM', 'HIGH']
RANKING_VALUES = {'UNDEFINED': 0, 'LOW': 1, 'MEDIUM': 2, 'HIGH': 3}
for rank in RANKING_VALUES:

View File

@ -11,7 +11,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
import unittest
from syntribos.formatters.json_formatter import JSONFormatter
@ -38,11 +37,6 @@ class IssueTestResult(unittest.TextTestResult):
"""
self.failures.append((test, test.failures))
self.stats["failures"] += len(test.failures)
if self.showAll:
sys.stdout.write("FAIL\n")
elif self.dots:
sys.stdout.write('F')
sys.stdout.flush()
def addError(self, test, err):
"""Duplicates parent class addError functionality.
@ -54,11 +48,6 @@ class IssueTestResult(unittest.TextTestResult):
"""
self.errors.append((test, self._exc_info_to_string(err, test)))
self.stats["errors"] += 1
if self.showAll:
sys.stdout.write("ERROR\n")
elif self.dots:
sys.stdout.write('E')
sys.stdout.flush()
def addSuccess(self, test):
"""Duplicates parent class addSuccess functionality.
@ -67,11 +56,6 @@ class IssueTestResult(unittest.TextTestResult):
:type test: :class:`syntribos.tests.base.BaseTestCase`
"""
self.stats["successes"] += 1
if self.showAll:
sys.stdout.write("ok\n")
elif self.dots:
sys.stdout.write('.')
sys.stdout.flush()
def printErrors(self, output_format, min_severity, min_confidence):
"""Print out each :class:`syntribos.issue.Issue` that was encountered
@ -82,8 +66,6 @@ class IssueTestResult(unittest.TextTestResult):
"json": JSONFormatter(self)
}
formatter = formatter_types[output_format]
if self.dots or self.showAll:
self.stream.writeln()
formatter.report(min_severity, min_confidence)
def stopTestRun(self):

View File

@ -25,6 +25,8 @@ import syntribos.config
from syntribos.result import IssueTestResult
import syntribos.tests as tests
import syntribos.tests.base
from syntribos.utils.ascii_colors import colorize
from syntribos.utils.progress_bar import ProgressBar
result = None
CONF = cfg.CONF
@ -36,13 +38,13 @@ class Runner(object):
log_file = ""
@classmethod
def print_tests(cls, list_tests=False):
def print_tests(cls):
"""Print out the list of available tests types that can be run."""
if list_tests:
testlist = []
print("Test types...:")
testlist = [name for name, _ in cls.get_tests()]
print(testlist)
testlist = []
print("Test types...:")
testlist = [name for name, _ in cls.get_tests()]
print(testlist)
exit(0)
@classmethod
def load_modules(cls, package):
@ -109,7 +111,7 @@ class Runner(object):
test_log = cls.get_log_file_name()
if test_log:
print(syntribos.SEP)
print("LOG PATH..........: {path}".format(path=test_log))
print("LOG PATH...: {path}".format(path=test_log))
print(syntribos.SEP)
@classmethod
@ -128,6 +130,7 @@ class Runner(object):
@classmethod
def run(cls):
global result
test_id = 1000
try:
try:
syntribos.config.register_opts()
@ -142,32 +145,66 @@ class Runner(object):
cls.print_symbol()
# 2 == higher verbosity, 1 == normal
verbosity = 1
verbosity = 0
if not CONF.outfile:
decorator = unittest.runner._WritelnDecorator(sys.stdout)
else:
decorator = unittest.runner._WritelnDecorator(
open(CONF.outfile, 'w'))
result = IssueTestResult(decorator, True, verbosity)
start_time = time.time()
if not CONF.list_tests:
for file_path, req_str in CONF.syntribos.templates:
for test_name, test_class in cls.get_tests(
CONF.test_types, CONF.excluded_types):
test_class.send_init_request(file_path, req_str)
for test in test_class.get_test_cases(file_path,
req_str):
if CONF.list_tests:
cls.print_tests()
print("\nRunning Tests...:")
for file_path, req_str in CONF.syntribos.templates:
print(syntribos.SEP)
print("Template File...: {}".format(file_path))
print(syntribos.SEP)
print("\n ID \t\tTest Name \t\t\t\t\t\tProgress")
list_of_tests = list(cls.get_tests(CONF.test_types,
CONF.excluded_types))
for test_name, test_class in list_of_tests:
test_id += 5
log_string = "[{test_id}] : {name}".format(
test_id=test_id, name=test_name)
result_string = "[{test_id}] : {name}".format(
test_id=colorize(test_id, color="green"),
name=test_name.replace("_", " ").capitalize())
if not CONF.colorize:
result_string = result_string.ljust(55)
else:
result_string = result_string.ljust(60)
LOG.debug(log_string)
test_class.send_init_request(file_path, req_str)
test_cases = list(
test_class.get_test_cases(file_path, req_str))
if len(test_cases) > 0:
bar = ProgressBar(message=result_string,
max=len(test_cases))
for test in test_cases:
if test:
test_time = cls.run_test(test, result,
CONF.dry_run)
test_time = "Test run time: {} sec.".format(
test_time)
LOG.debug(test_time)
bar.increment(1)
bar.print_bar()
failures = len(test.failures)
total_tests = len(test_cases)
if failures > total_tests * 0.90:
# More than 90 percent failure
failures = colorize(failures, "red")
elif failures > total_tests * 0.45:
# More than 45 percent failure
failures = colorize(failures, "yellow")
elif failures > total_tests * 0.15:
# More than 15 percent failure
failures = colorize(failures, "blue")
print(" : {} Failure(s)\r".format(failures))
print(syntribos.SEP)
print("\nResults...:\n")
cls.print_result(result, start_time)
else:
cls.print_tests(CONF.list_tests)
except KeyboardInterrupt:
cls.print_result(result, start_time)
print("Keyboard interrupt, exiting...")
@ -184,7 +221,6 @@ class Runner(object):
"""
suite = unittest.TestSuite()
test_start_time = time.time()
suite.addTest(test("run_test_case"))
if dry_run:
for test in suite:

View File

@ -168,7 +168,6 @@ class BaseTestCase(unittest.TestCase):
def tearDown(cls):
get_slugs = [sig.slug for sig in cls.test_signals]
get_checks = [sig.check_name for sig in cls.test_signals]
test_signals_used = "Signals: " + str(get_slugs)
LOG.debug(test_signals_used)
test_checks_used = "Checks used: " + str(get_checks)

View File

@ -113,6 +113,7 @@ class BaseFuzzTestCase(base.BaseTestCase):
string used as a fuzz test payload entails the generation of a new
subclass for each parameter fuzzed. See :func:`base.extend_class`.
"""
cls.failures = []
prefix_name = "{filename}_{test_name}_{fuzz_file}_".format(
filename=filename, test_name=cls.test_name, fuzz_file=cls.data_key)

View File

@ -17,7 +17,7 @@ from syntribos.tests.fuzz import base_fuzz
class IntOverflowBody(base_fuzz.BaseFuzzTestCase):
test_name = "INT_OVERFLOW_BODY"
test_name = "INTEGER_OVERFLOW_BODY"
test_type = "data"
data_key = "integer-overflow.txt"
@ -35,16 +35,16 @@ class IntOverflowBody(base_fuzz.BaseFuzzTestCase):
class IntOverflowParams(IntOverflowBody):
test_name = "INT_OVERFLOW_PARAMS"
test_name = "INTEGER_OVERFLOW_PARAMS"
test_type = "params"
class IntOverflowHeaders(IntOverflowBody):
test_name = "INT_OVERFLOW_HEADERS"
test_name = "INTEGER_OVERFLOW_HEADERS"
test_type = "headers"
class IntOverflowURL(IntOverflowBody):
test_name = "INT_OVERFLOW_URL"
test_name = "INTEGER_OVERFLOW_URL"
test_type = "url"
url_var = "FUZZ"

View File

@ -16,22 +16,22 @@ from syntribos.tests.fuzz import base_fuzz
class StringValidationBody(base_fuzz.BaseFuzzTestCase):
test_name = "STRING_VALIDATION_VULNERABILITY_BODY"
test_name = "STRING_VALIDATION_BODY"
test_type = "data"
data_key = "string_validation.txt"
class StringValidationParams(StringValidationBody):
test_name = "STRING_VALIDATION_VULNERABILITY_PARAMS"
test_name = "STRING_VALIDATION_PARAMS"
test_type = "params"
class StringValidationHeaders(StringValidationBody):
test_name = "STRING_VALIDATION_VULNERABILITY_HEADERS"
test_name = "STRING_VALIDATION_HEADERS"
test_type = "headers"
class StringValidationURL(StringValidationBody):
test_name = "STRING_VALIDATION_VULNERABILITY_URL"
test_name = "STRING_VALIDATION_URL"
test_type = "url"
url_var = "FUZZ"

View File

@ -32,7 +32,7 @@ class CorsHeader(base.BaseTestCase):
it is registered as a signal and an issue is raised.
"""
test_name = "CORS_HEADER"
test_name = "CORS_WILDCARD_HEADERS"
test_type = "headers"
client = client()
failures = []

View File

@ -25,8 +25,8 @@ CONF = cfg.CONF
class SSLTestCase(base.BaseTestCase):
test_name = "SSL"
test_type = "headers"
test_name = "SSL_ENDPOINT_BODY"
test_type = "body"
client = client()
failures = []

View File

View File

@ -0,0 +1,30 @@
# Copyright 2016 Intel
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo_config import cfg
CONF = cfg.CONF
def colorize(string, color="nocolor"):
"""A simple method to add ascii colors to the terminal."""
color_names = ["red", "green", "yellow", "blue"]
colors = dict(zip(color_names, range(31, 35)))
colors["nocolor"] = 0 # No Color
if not CONF.colorize:
return string
return "\033[0;{color}m{string}\033[0;m".format(string=string,
color=colors.setdefault(
color, 0))

View File

@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
# Copyright 2016 Intel
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import division
from __future__ import unicode_literals
from math import ceil
import sys
class ProgressBar(object):
"""A simple progressBar.
A simple generic progress bar like many others.
:param int max: max value, when progress is 100 %
:param int width: width of the progress bar
:param str fill_char: character to show progress
:param str empty_char: character to show empty part
:param str message: string to be part of the progress bar
"""
def __init__(self, max=30, width=23, fill_char="", empty_char="-",
message=""):
self.width = width
self.max = max
self.fill_char = fill_char if fill_char else ""
self.empty_char = empty_char
self.message = message
self.present_level = 0
def increment(self, inc_level=1):
"""Method to increment the progress.
:param int inc_level: level of increment
:returns: None
"""
if self.max > self.present_level + inc_level:
self.present_level += inc_level
else:
self.present_level = self.max
def format_bar(self):
"""Method to format the progress bar.
This method appends the message string and the progress bar,
also calculates the percentage of progress and appends it
to the formatted progress bar
:returns: formatted progress bar string
"""
bar_width = int(ceil(self.present_level / self.max * self.width))
empty_char = self.empty_char * (self.width - bar_width)
fill_char = self.fill_char * bar_width
percentage = int(self.present_level / self.max * 100)
return "{message}\t\t|{fill_char}{empty_char}| {percentage} %".format(
message=self.message, fill_char=fill_char,
empty_char=empty_char, percentage=percentage)
def print_bar(self):
"""As the method says, prints the bar to standard out."""
sys.stdout.write("\r")
sys.stdout.write((self.format_bar()))
sys.stdout.flush()

View File

@ -0,0 +1,39 @@
# Copyright 2016 Intel
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import testtools
from syntribos.utils.ascii_colors import colorize
from syntribos.utils.ascii_colors import CONF
class TestColorize(testtools.TestCase):
def test_colorize(self):
CONF.colorize = True
string = "color this string"
colors = {"red": 31,
"green": 32,
"yellow": 33,
"blue": 34,
"nocolor": 0}
for color in colors:
self.assertEqual(
"\033[0;{clr}m{string}\033[0;m".format(
string=string, clr=colors[color]),
colorize(string, color))
def test_no_colorize(self):
CONF.colorize = False
string = "No color"
self.assertEqual(string, colorize(string))

View File

@ -0,0 +1,43 @@
# Copyright 2016 Intel
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import testtools
from syntribos.utils.progress_bar import ProgressBar
class TestProgressBar(testtools.TestCase):
def test_pb(self):
pb = ProgressBar(fill_char="#", message="Test")
self.assertEqual(pb.max, 30)
self.assertEqual(pb.width, 23)
self.assertEqual(pb.fill_char, "#")
self.assertEqual(pb.message, "Test")
def test_increment(self):
pb = ProgressBar()
pb.increment(10)
self.assertEqual(10, pb.present_level)
pb.increment(20)
self.assertEqual(pb.present_level, pb.max)
def test_format_bar(self):
pb = ProgressBar(max=5, width=5, fill_char="#", message="Test")
pb.increment() # increments progress bar by 1
self.assertEqual(u"Test\t\t|#----| 20 %", pb.format_bar())
def test_print_bar(self):
pb = ProgressBar(max=5, width=5, fill_char="#", message="Test")
pb.increment() # increments progress bar by 1
self.assertIsNone(pb.print_bar())

View File

@ -0,0 +1,51 @@
# Copyright 2016 Intel
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
import testtools
from syntribos.result import IssueTestResult
class FakeTest(object):
def __init__(self, name):
self.failures = [1, 2]
self.errors = [3, 4]
self.successes = [5, 6]
self.name = name
self.failureException = Exception
def __str__(self):
return self.name
class TestIssueTestResult(testtools.TestCase):
"""Class to test methods in IssueTestResult class."""
issue_result = IssueTestResult(None, False, 0)
def test_addFailure(self):
test = FakeTest("failure")
self.issue_result.addFailure(test, ())
self.assertEqual(self.issue_result.stats["failures"], 2)
def test_addError(self):
test = FakeTest("error")
self.issue_result.addError(test, sys.exc_info())
self.assertEqual(self.issue_result.stats["errors"], 1)
def test_addSuccess(self):
test = FakeTest("success")
self.issue_result.addSuccess(test)
self.assertEqual(self.issue_result.stats["successes"], 1)

View File

@ -53,8 +53,8 @@ class RunnerUnittest(testtools.TestCase):
def test_get_int_overflow_tests(self):
"""Check that we get the proper integer overflow tests."""
expected = ["INT_OVERFLOW_" + x for x in self.common_endings]
loaded_tests = self.r.get_tests(["INT_OVERFLOW"])
expected = ["INTEGER_OVERFLOW_" + x for x in self.common_endings]
loaded_tests = self.r.get_tests(["INTEGER_OVERFLOW"])
self._compare_tests(expected, loaded_tests)
def test_get_buffer_overflow_tests(self):
@ -72,7 +72,7 @@ class RunnerUnittest(testtools.TestCase):
def test_get_string_validation_tests(self):
"""Check that we get the proper string validation tests."""
expected = [
"STRING_VALIDATION_VULNERABILITY_" + x for x in self.common_endings
"STRING_VALIDATION_" + x for x in self.common_endings
]
loaded_tests = self.r.get_tests(["STRING_VALIDATION"])
self._compare_tests(expected, loaded_tests)
@ -85,14 +85,14 @@ class RunnerUnittest(testtools.TestCase):
def test_get_ssl_test(self):
"""Check that we get only the SSL test from get_tests."""
expected = ["SSL"]
expected = ["SSL_ENDPOINT_BODY"]
loaded_tests = self.r.get_tests(["SSL"])
self._compare_tests(expected, loaded_tests)
def test_get_cors_test(self):
"""Check that we get only the CORS_HEADER test from get_tests."""
expected = ["CORS_HEADER"]
loaded_tests = self.r.get_tests(["CORS_HEADER"])
expected = ["CORS_WILDCARD_HEADERS"]
loaded_tests = self.r.get_tests(["CORS_WILDCARD_HEADERS"])
self._compare_tests(expected, loaded_tests)
def test_log_path_caching(self):