Merge "Add request/response to subunit-describe-calls"
This commit is contained in:
commit
de34d556e0
|
@ -1,4 +1,8 @@
|
||||||
---
|
---
|
||||||
features:
|
features:
|
||||||
- Adds subunit-describe-calls. A parser for subunit streams to determine what
|
- |
|
||||||
|
Adds subunit-describe-calls. A parser for subunit streams to determine what
|
||||||
REST API calls are made inside of a test and in what order they are called.
|
REST API calls are made inside of a test and in what order they are called.
|
||||||
|
|
||||||
|
* Input can be piped in or a file can be specified
|
||||||
|
* Output is shortened for stdout, the output file has more information
|
||||||
|
|
|
@ -21,13 +21,14 @@ API calls are made inside of a test and in what order they are called.
|
||||||
Runtime Arguments
|
Runtime Arguments
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
**--subunit, -s**: (Required) The path to the subunit file being parsed
|
**--subunit, -s**: (Optional) The path to the subunit file being parsed,
|
||||||
|
defaults to stdin
|
||||||
|
|
||||||
**--non-subunit-name, -n**: (Optional) The file_name that the logs are being
|
**--non-subunit-name, -n**: (Optional) The file_name that the logs are being
|
||||||
stored in
|
stored in
|
||||||
|
|
||||||
**--output-file, -o**: (Required) The path where the JSON output will be
|
**--output-file, -o**: (Optional) The path where the JSON output will be
|
||||||
written to
|
written to. This contains more information than is present in stdout.
|
||||||
|
|
||||||
**--ports, -p**: (Optional) The path to a JSON file describing the ports being
|
**--ports, -p**: (Optional) The path to a JSON file describing the ports being
|
||||||
used by different services
|
used by different services
|
||||||
|
@ -35,13 +36,14 @@ used by different services
|
||||||
Usage
|
Usage
|
||||||
-----
|
-----
|
||||||
|
|
||||||
subunit-describe-calls will take in a file path via the --subunit parameter
|
subunit-describe-calls will take in either stdin subunit v1 or v2 stream or a
|
||||||
which contains either a subunit v1 or v2 stream. This is then parsed checking
|
file path which contains either a subunit v1 or v2 stream passed via the
|
||||||
for details contained in the file_bytes of the --non-subunit-name parameter
|
--subunit parameter. This is then parsed checking for details contained in the
|
||||||
(the default is pythonlogging which is what Tempest uses to store logs). By
|
file_bytes of the --non-subunit-name parameter (the default is pythonlogging
|
||||||
default the OpenStack Kilo release port defaults (http://bit.ly/22jpF5P)
|
which is what Tempest uses to store logs). By default the OpenStack Kilo
|
||||||
are used unless a file is provided via the --ports option. The resulting output
|
release port defaults (http://bit.ly/22jpF5P) are used unless a file is
|
||||||
is dumped in JSON output to the path provided in the --output-file option.
|
provided via the --ports option. The resulting output is dumped in JSON output
|
||||||
|
to the path provided in the --output-file option.
|
||||||
|
|
||||||
Ports file JSON structure
|
Ports file JSON structure
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
@ -64,7 +66,11 @@ Output file JSON structure
|
||||||
"verb": "HTTP Verb",
|
"verb": "HTTP Verb",
|
||||||
"service": "Name of the service",
|
"service": "Name of the service",
|
||||||
"url": "A shortened version of the URL called",
|
"url": "A shortened version of the URL called",
|
||||||
"status_code": "The status code of the response"
|
"status_code": "The status code of the response",
|
||||||
|
"request_headers": "The headers of the request",
|
||||||
|
"request_body": "The body of the request",
|
||||||
|
"response_headers": "The headers of the response",
|
||||||
|
"response_body": "The body of the response"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -75,6 +81,7 @@ import io
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
import subunit
|
import subunit
|
||||||
import testtools
|
import testtools
|
||||||
|
@ -91,6 +98,9 @@ class UrlParser(testtools.TestResult):
|
||||||
'(?P<verb>\w*) (?P<url>.*) .*')
|
'(?P<verb>\w*) (?P<url>.*) .*')
|
||||||
port_re = re.compile(r'.*:(?P<port>\d+).*')
|
port_re = re.compile(r'.*:(?P<port>\d+).*')
|
||||||
path_re = re.compile(r'http[s]?://[^/]*/(?P<path>.*)')
|
path_re = re.compile(r'http[s]?://[^/]*/(?P<path>.*)')
|
||||||
|
request_re = re.compile(r'.* Request - Headers: (?P<headers>.*)')
|
||||||
|
response_re = re.compile(r'.* Response - Headers: (?P<headers>.*)')
|
||||||
|
body_re = re.compile(r'.*Body: (?P<body>.*)')
|
||||||
|
|
||||||
# Based on mitaka defaults:
|
# Based on mitaka defaults:
|
||||||
# http://docs.openstack.org/mitaka/config-reference/
|
# http://docs.openstack.org/mitaka/config-reference/
|
||||||
|
@ -151,15 +161,46 @@ class UrlParser(testtools.TestResult):
|
||||||
|
|
||||||
calls = []
|
calls = []
|
||||||
for _, detail in details.items():
|
for _, detail in details.items():
|
||||||
|
in_request = False
|
||||||
|
in_response = False
|
||||||
|
current_call = {}
|
||||||
for line in detail.as_text().split("\n"):
|
for line in detail.as_text().split("\n"):
|
||||||
match = self.url_re.match(line)
|
url_match = self.url_re.match(line)
|
||||||
if match is not None:
|
request_match = self.request_re.match(line)
|
||||||
calls.append({
|
response_match = self.response_re.match(line)
|
||||||
"name": match.group("name"),
|
body_match = self.body_re.match(line)
|
||||||
"verb": match.group("verb"),
|
|
||||||
"status_code": match.group("code"),
|
if url_match is not None:
|
||||||
"service": self.get_service(match.group("url")),
|
if current_call != {}:
|
||||||
"url": self.url_path(match.group("url"))})
|
calls.append(current_call.copy())
|
||||||
|
current_call = {}
|
||||||
|
in_request, in_response = False, False
|
||||||
|
current_call.update({
|
||||||
|
"name": url_match.group("name"),
|
||||||
|
"verb": url_match.group("verb"),
|
||||||
|
"status_code": url_match.group("code"),
|
||||||
|
"service": self.get_service(url_match.group("url")),
|
||||||
|
"url": self.url_path(url_match.group("url"))})
|
||||||
|
elif request_match is not None:
|
||||||
|
in_request, in_response = True, False
|
||||||
|
current_call.update(
|
||||||
|
{"request_headers": request_match.group("headers")})
|
||||||
|
elif in_request and body_match is not None:
|
||||||
|
in_request = False
|
||||||
|
current_call.update(
|
||||||
|
{"request_body": body_match.group(
|
||||||
|
"body")})
|
||||||
|
elif response_match is not None:
|
||||||
|
in_request, in_response = False, True
|
||||||
|
current_call.update(
|
||||||
|
{"response_headers": response_match.group(
|
||||||
|
"headers")})
|
||||||
|
elif in_response and body_match is not None:
|
||||||
|
in_response = False
|
||||||
|
current_call.update(
|
||||||
|
{"response_body": body_match.group("body")})
|
||||||
|
if current_call != {}:
|
||||||
|
calls.append(current_call.copy())
|
||||||
|
|
||||||
return calls
|
return calls
|
||||||
|
|
||||||
|
@ -206,8 +247,9 @@ class ArgumentParser(argparse.ArgumentParser):
|
||||||
self.prog = "subunit-describe-calls"
|
self.prog = "subunit-describe-calls"
|
||||||
|
|
||||||
self.add_argument(
|
self.add_argument(
|
||||||
"-s", "--subunit", metavar="<subunit file>", required=True,
|
"-s", "--subunit", metavar="<subunit file>",
|
||||||
default=None, help="The path to the subunit output file.")
|
nargs="?", type=argparse.FileType('rb'), default=sys.stdin,
|
||||||
|
help="The path to the subunit output file.")
|
||||||
|
|
||||||
self.add_argument(
|
self.add_argument(
|
||||||
"-n", "--non-subunit-name", metavar="<non subunit name>",
|
"-n", "--non-subunit-name", metavar="<non subunit name>",
|
||||||
|
@ -216,19 +258,18 @@ class ArgumentParser(argparse.ArgumentParser):
|
||||||
|
|
||||||
self.add_argument(
|
self.add_argument(
|
||||||
"-o", "--output-file", metavar="<output file>", default=None,
|
"-o", "--output-file", metavar="<output file>", default=None,
|
||||||
help="The output file name for the json.", required=True)
|
help="The output file name for the json.")
|
||||||
|
|
||||||
self.add_argument(
|
self.add_argument(
|
||||||
"-p", "--ports", metavar="<ports file>", default=None,
|
"-p", "--ports", metavar="<ports file>", default=None,
|
||||||
help="A JSON file describing the ports for each service.")
|
help="A JSON file describing the ports for each service.")
|
||||||
|
|
||||||
|
|
||||||
def parse(subunit_file, non_subunit_name, ports):
|
def parse(stream, non_subunit_name, ports):
|
||||||
if ports is not None and os.path.exists(ports):
|
if ports is not None and os.path.exists(ports):
|
||||||
ports = json.loads(open(ports).read())
|
ports = json.loads(open(ports).read())
|
||||||
|
|
||||||
url_parser = UrlParser(ports)
|
url_parser = UrlParser(ports)
|
||||||
stream = open(subunit_file, 'rb')
|
|
||||||
suite = subunit.ByteStreamToStreamResult(
|
suite = subunit.ByteStreamToStreamResult(
|
||||||
stream, non_subunit_name=non_subunit_name)
|
stream, non_subunit_name=non_subunit_name)
|
||||||
result = testtools.StreamToExtendedDecorator(url_parser)
|
result = testtools.StreamToExtendedDecorator(url_parser)
|
||||||
|
@ -248,8 +289,21 @@ def parse(subunit_file, non_subunit_name, ports):
|
||||||
|
|
||||||
|
|
||||||
def output(url_parser, output_file):
|
def output(url_parser, output_file):
|
||||||
with open(output_file, "w") as outfile:
|
if output_file is not None:
|
||||||
outfile.write(json.dumps(url_parser.test_logs))
|
with open(output_file, "w") as outfile:
|
||||||
|
outfile.write(json.dumps(url_parser.test_logs))
|
||||||
|
return
|
||||||
|
|
||||||
|
for test_name, items in url_parser.test_logs.iteritems():
|
||||||
|
sys.stdout.write('{0}\n'.format(test_name))
|
||||||
|
if not items:
|
||||||
|
sys.stdout.write('\n')
|
||||||
|
continue
|
||||||
|
for item in items:
|
||||||
|
sys.stdout.write('\t- {0} {1} request for {2} to {3}\n'.format(
|
||||||
|
item.get('status_code'), item.get('verb'),
|
||||||
|
item.get('service'), item.get('url')))
|
||||||
|
sys.stdout.write('\n')
|
||||||
|
|
||||||
|
|
||||||
def entry_point():
|
def entry_point():
|
||||||
|
|
|
@ -38,46 +38,159 @@ class TestSubunitDescribeCalls(base.TestCase):
|
||||||
os.path.dirname(os.path.abspath(__file__)),
|
os.path.dirname(os.path.abspath(__file__)),
|
||||||
'sample_streams/calls.subunit')
|
'sample_streams/calls.subunit')
|
||||||
parser = subunit_describe_calls.parse(
|
parser = subunit_describe_calls.parse(
|
||||||
subunit_file, "pythonlogging", None)
|
open(subunit_file), "pythonlogging", None)
|
||||||
expected_result = {
|
expected_result = {
|
||||||
'bar': [{'name': 'AgentsAdminTestJSON:setUp',
|
'bar': [{
|
||||||
'service': 'Nova',
|
'name': 'AgentsAdminTestJSON:setUp',
|
||||||
'status_code': '200',
|
'request_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
|
||||||
'url': 'v2.1/<id>/os-agents',
|
'"hypervisor": "common", "md5hash": '
|
||||||
'verb': 'POST'},
|
'"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
|
||||||
{'name': 'AgentsAdminTestJSON:test_create_agent',
|
'"architecture": "tempest-x86_64-424013832", "os": "linux"}}',
|
||||||
'service': 'Nova',
|
'request_headers': "{'Content-Type': 'application/json', "
|
||||||
'status_code': '200',
|
"'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
|
||||||
'url': 'v2.1/<id>/os-agents',
|
'response_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
|
||||||
'verb': 'POST'},
|
'"hypervisor": "common", "md5hash": '
|
||||||
{'name': 'AgentsAdminTestJSON:tearDown',
|
'"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
|
||||||
'service': 'Nova',
|
'"architecture": "tempest-x86_64-424013832", "os": "linux", '
|
||||||
'status_code': '200',
|
'"agent_id": 1}}',
|
||||||
'url': 'v2.1/<id>/os-agents/1',
|
'response_headers': "{'status': '200', 'content-length': "
|
||||||
'verb': 'DELETE'},
|
"'203', 'x-compute-request-id': "
|
||||||
{'name': 'AgentsAdminTestJSON:_run_cleanups',
|
"'req-25ddaae2-0ef1-40d1-8228-59bd64a7e75b', 'vary': "
|
||||||
'service': 'Nova',
|
"'X-OpenStack-Nova-API-Version', 'connection': 'close', "
|
||||||
'status_code': '200',
|
"'x-openstack-nova-api-version': '2.1', 'date': "
|
||||||
'url': 'v2.1/<id>/os-agents/2',
|
"'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': "
|
||||||
'verb': 'DELETE'}],
|
"'application/json'}",
|
||||||
'foo': [{'name': 'AgentsAdminTestJSON:setUp',
|
'service': 'Nova',
|
||||||
'service': 'Nova',
|
'status_code': '200',
|
||||||
'status_code': '200',
|
'url': 'v2.1/<id>/os-agents',
|
||||||
'url': 'v2.1/<id>/os-agents',
|
'verb': 'POST'}, {
|
||||||
'verb': 'POST'},
|
'name': 'AgentsAdminTestJSON:test_create_agent',
|
||||||
{'name': 'AgentsAdminTestJSON:test_delete_agent',
|
'request_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
|
||||||
'service': 'Nova',
|
'"hypervisor": "kvm", "md5hash": '
|
||||||
'status_code': '200',
|
'"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
|
||||||
'url': 'v2.1/<id>/os-agents/3',
|
'"architecture": "tempest-x86-252246646", "os": "win"}}',
|
||||||
'verb': 'DELETE'},
|
'request_headers': "{'Content-Type': 'application/json', "
|
||||||
{'name': 'AgentsAdminTestJSON:test_delete_agent',
|
"'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
|
||||||
'service': 'Nova',
|
'response_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
|
||||||
'status_code': '200',
|
'"hypervisor": "kvm", "md5hash": '
|
||||||
'url': 'v2.1/<id>/os-agents',
|
'"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
|
||||||
'verb': 'GET'},
|
'"architecture": "tempest-x86-252246646", "os": "win", '
|
||||||
{'name': 'AgentsAdminTestJSON:tearDown',
|
'"agent_id": 2}}',
|
||||||
'service': 'Nova',
|
'response_headers': "{'status': '200', 'content-length': "
|
||||||
'status_code': '404',
|
"'195', 'x-compute-request-id': "
|
||||||
'url': 'v2.1/<id>/os-agents/3',
|
"'req-b4136f06-c015-4e7e-995f-c43831e3ecce', 'vary': "
|
||||||
'verb': 'DELETE'}]}
|
"'X-OpenStack-Nova-API-Version', 'connection': 'close', "
|
||||||
|
"'x-openstack-nova-api-version': '2.1', 'date': "
|
||||||
|
"'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': "
|
||||||
|
"'application/json'}",
|
||||||
|
'service': 'Nova',
|
||||||
|
'status_code': '200',
|
||||||
|
'url': 'v2.1/<id>/os-agents',
|
||||||
|
'verb': 'POST'}, {
|
||||||
|
'name': 'AgentsAdminTestJSON:tearDown',
|
||||||
|
'request_body': 'None',
|
||||||
|
'request_headers': "{'Content-Type': 'application/json', "
|
||||||
|
"'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
|
||||||
|
'response_body': '',
|
||||||
|
'response_headers': "{'status': '200', 'content-length': "
|
||||||
|
"'0', 'x-compute-request-id': "
|
||||||
|
"'req-ee905fd6-a5b5-4da4-8c37-5363cb25bd9d', 'vary': "
|
||||||
|
"'X-OpenStack-Nova-API-Version', 'connection': 'close', "
|
||||||
|
"'x-openstack-nova-api-version': '2.1', 'date': "
|
||||||
|
"'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': "
|
||||||
|
"'application/json'}",
|
||||||
|
'service': 'Nova',
|
||||||
|
'status_code': '200',
|
||||||
|
'url': 'v2.1/<id>/os-agents/1',
|
||||||
|
'verb': 'DELETE'}, {
|
||||||
|
'name': 'AgentsAdminTestJSON:_run_cleanups',
|
||||||
|
'request_body': 'None',
|
||||||
|
'request_headers': "{'Content-Type': 'application/json', "
|
||||||
|
"'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
|
||||||
|
'response_headers': "{'status': '200', 'content-length': "
|
||||||
|
"'0', 'x-compute-request-id': "
|
||||||
|
"'req-e912cac0-63e0-4679-a68a-b6d18ddca074', 'vary': "
|
||||||
|
"'X-OpenStack-Nova-API-Version', 'connection': 'close', "
|
||||||
|
"'x-openstack-nova-api-version': '2.1', 'date': "
|
||||||
|
"'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': "
|
||||||
|
"'application/json'}",
|
||||||
|
'service': 'Nova',
|
||||||
|
'status_code': '200',
|
||||||
|
'url': 'v2.1/<id>/os-agents/2',
|
||||||
|
'verb': 'DELETE'}],
|
||||||
|
'foo': [{
|
||||||
|
'name': 'AgentsAdminTestJSON:setUp',
|
||||||
|
'request_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
|
||||||
|
'"hypervisor": "common", "md5hash": '
|
||||||
|
'"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
|
||||||
|
'"architecture": "tempest-x86_64-948635295", "os": "linux"}}',
|
||||||
|
'request_headers': "{'Content-Type': 'application/json', "
|
||||||
|
"'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
|
||||||
|
'response_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", '
|
||||||
|
'"hypervisor": "common", "md5hash": '
|
||||||
|
'"add6bb58e139be103324d04d82d8f545", "version": "7.0", '
|
||||||
|
'"architecture": "tempest-x86_64-948635295", "os": "linux", '
|
||||||
|
'"agent_id": 3}}',
|
||||||
|
'response_headers': "{'status': '200', 'content-length': "
|
||||||
|
"'203', 'x-compute-request-id': "
|
||||||
|
"'req-ccd2116d-04b1-4ffe-ae32-fb623f68bf1c', 'vary': "
|
||||||
|
"'X-OpenStack-Nova-API-Version', 'connection': 'close', "
|
||||||
|
"'x-openstack-nova-api-version': '2.1', 'date': "
|
||||||
|
"'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': "
|
||||||
|
"'application/json'}",
|
||||||
|
'service': 'Nova',
|
||||||
|
'status_code': '200',
|
||||||
|
'url': 'v2.1/<id>/os-agents',
|
||||||
|
'verb': 'POST'}, {
|
||||||
|
'name': 'AgentsAdminTestJSON:test_delete_agent',
|
||||||
|
'request_body': 'None',
|
||||||
|
'request_headers': "{'Content-Type': 'application/json', "
|
||||||
|
"'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
|
||||||
|
'response_body': '',
|
||||||
|
'response_headers': "{'status': '200', 'content-length': "
|
||||||
|
"'0', 'x-compute-request-id': "
|
||||||
|
"'req-6e7fa28f-ae61-4388-9a78-947c58bc0588', 'vary': "
|
||||||
|
"'X-OpenStack-Nova-API-Version', 'connection': 'close', "
|
||||||
|
"'x-openstack-nova-api-version': '2.1', 'date': "
|
||||||
|
"'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': "
|
||||||
|
"'application/json'}",
|
||||||
|
'service': 'Nova',
|
||||||
|
'status_code': '200',
|
||||||
|
'url': 'v2.1/<id>/os-agents/3',
|
||||||
|
'verb': 'DELETE'}, {
|
||||||
|
'name': 'AgentsAdminTestJSON:test_delete_agent',
|
||||||
|
'request_body': 'None',
|
||||||
|
'request_headers': "{'Content-Type': 'application/json', "
|
||||||
|
"'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
|
||||||
|
'response_body': '{"agents": []}',
|
||||||
|
'response_headers': "{'status': '200', 'content-length': "
|
||||||
|
"'14', 'content-location': "
|
||||||
|
"'http://23.253.76.97:8774/v2.1/"
|
||||||
|
"cf6b1933fe5b476fbbabb876f6d1b924/os-agents', "
|
||||||
|
"'x-compute-request-id': "
|
||||||
|
"'req-e41aa9b4-41a6-4138-ae04-220b768eb644', 'vary': "
|
||||||
|
"'X-OpenStack-Nova-API-Version', 'connection': 'close', "
|
||||||
|
"'x-openstack-nova-api-version': '2.1', 'date': "
|
||||||
|
"'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': "
|
||||||
|
"'application/json'}",
|
||||||
|
'service': 'Nova',
|
||||||
|
'status_code': '200',
|
||||||
|
'url': 'v2.1/<id>/os-agents',
|
||||||
|
'verb': 'GET'}, {
|
||||||
|
'name': 'AgentsAdminTestJSON:tearDown',
|
||||||
|
'request_body': 'None',
|
||||||
|
'request_headers': "{'Content-Type': 'application/json', "
|
||||||
|
"'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}",
|
||||||
|
'response_headers': "{'status': '404', 'content-length': "
|
||||||
|
"'82', 'x-compute-request-id': "
|
||||||
|
"'req-e297aeea-91cf-4f26-b49c-8f46b1b7a926', 'vary': "
|
||||||
|
"'X-OpenStack-Nova-API-Version', 'connection': 'close', "
|
||||||
|
"'x-openstack-nova-api-version': '2.1', 'date': "
|
||||||
|
"'Tue, 02 Feb 2016 03:27:02 GMT', 'content-type': "
|
||||||
|
"'application/json; charset=UTF-8'}",
|
||||||
|
'service': 'Nova',
|
||||||
|
'status_code': '404',
|
||||||
|
'url': 'v2.1/<id>/os-agents/3',
|
||||||
|
'verb': 'DELETE'}]}
|
||||||
|
|
||||||
self.assertEqual(expected_result, parser.test_logs)
|
self.assertEqual(expected_result, parser.test_logs)
|
||||||
|
|
Loading…
Reference in New Issue