squad 1.88__py3-none-any.whl → 1.90__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of squad might be problematic. Click here for more details.
- squad/api/rest.py +7 -1
- squad/api/views.py +1 -1
- squad/ci/backend/lava.py +3 -2
- squad/ci/tasks.py +6 -2
- squad/core/callback.py +10 -4
- squad/core/history.py +4 -1
- squad/core/management/commands/import_data.py +2 -1
- squad/core/models.py +17 -5
- squad/core/tasks/__init__.py +3 -3
- squad/core/utils.py +8 -5
- squad/frontend/comparison.py +6 -1
- squad/frontend/tests.py +2 -2
- squad/plugins/lib/__init__.py +1 -0
- squad/plugins/lib/base_log_parser.py +152 -0
- squad/plugins/linux_log_parser.py +18 -86
- squad/settings.py +15 -11
- squad/version.py +1 -1
- {squad-1.88.dist-info → squad-1.90.dist-info}/METADATA +13 -12
- {squad-1.88.dist-info → squad-1.90.dist-info}/RECORD +23 -21
- {squad-1.88.dist-info → squad-1.90.dist-info}/WHEEL +1 -1
- {squad-1.88.dist-info → squad-1.90.dist-info}/entry_points.txt +1 -0
- {squad-1.88.dist-info → squad-1.90.dist-info}/COPYING +0 -0
- {squad-1.88.dist-info → squad-1.90.dist-info}/top_level.txt +0 -0
squad/api/rest.py
CHANGED
@@ -1190,7 +1190,13 @@ class BuildViewSet(NestedViewSetMixin, ModelViewSet):
|
|
1190
1190
|
raise ValidationError('url is required.')
|
1191
1191
|
return Response({'message': 'OK'}, status=status.HTTP_202_ACCEPTED)
|
1192
1192
|
except (ValidationError, IntegrityError) as e:
|
1193
|
-
|
1193
|
+
message = ""
|
1194
|
+
if hasattr(e, "messages"):
|
1195
|
+
message = ', '.join(e.messages)
|
1196
|
+
else:
|
1197
|
+
message = str(e)
|
1198
|
+
|
1199
|
+
return Response({'message': message}, status=status.HTTP_400_BAD_REQUEST)
|
1194
1200
|
|
1195
1201
|
@action(detail=True, methods=['get'], suffix='compare')
|
1196
1202
|
def compare(self, request, pk=None):
|
squad/api/views.py
CHANGED
@@ -105,7 +105,7 @@ def add_test_run(request, group_slug, project_slug, version, environment_slug):
|
|
105
105
|
if 'attachment' in request.FILES:
|
106
106
|
attachments = {}
|
107
107
|
for f in request.FILES.getlist('attachment'):
|
108
|
-
attachments[f.name] =
|
108
|
+
attachments[f.name] = f
|
109
109
|
test_run_data['attachments'] = attachments
|
110
110
|
|
111
111
|
receive = ReceiveTestRun(project)
|
squad/ci/backend/lava.py
CHANGED
@@ -396,9 +396,10 @@ class Backend(BaseBackend):
|
|
396
396
|
|
397
397
|
def __lava_job_name(self, definition):
|
398
398
|
yaml_definition = yaml.safe_load(definition)
|
399
|
-
if 'job_name' in yaml_definition.keys():
|
399
|
+
if type(yaml_definition) is dict and 'job_name' in yaml_definition.keys():
|
400
|
+
job_name = yaml_definition['job_name']
|
400
401
|
# only return first 255 characters
|
401
|
-
return
|
402
|
+
return job_name[:255] if job_name else ''
|
402
403
|
return None
|
403
404
|
|
404
405
|
def __resubmit__(self, job_id):
|
squad/ci/tasks.py
CHANGED
@@ -27,8 +27,12 @@ def poll(backend_id=None):
|
|
27
27
|
@celery.task
|
28
28
|
def fetch(job_id):
|
29
29
|
logger.info("fetching %s" % job_id)
|
30
|
-
|
31
|
-
|
30
|
+
try:
|
31
|
+
testjob = TestJob.objects.get(pk=job_id)
|
32
|
+
if testjob.job_id:
|
33
|
+
testjob.backend.fetch(testjob.id)
|
34
|
+
except TestJob.DoesNotExist:
|
35
|
+
return
|
32
36
|
|
33
37
|
|
34
38
|
@celery.task
|
squad/core/callback.py
CHANGED
@@ -56,12 +56,18 @@ def dispatch_callback(callback_object):
|
|
56
56
|
else:
|
57
57
|
args['data'] = json.loads(callback_object.payload)
|
58
58
|
|
59
|
-
|
60
|
-
|
59
|
+
try:
|
60
|
+
request = getattr(requests, callback_object.method)
|
61
|
+
response = request(callback_object.url, **args)
|
62
|
+
response_code = response.status_code
|
63
|
+
response_content = response.content
|
64
|
+
except requests.exceptions.RequestException as e:
|
65
|
+
response_code = None
|
66
|
+
response_content = str(e)
|
61
67
|
|
62
68
|
if callback_object.record_response:
|
63
|
-
callback_object.response_code =
|
64
|
-
callback_object.response_content =
|
69
|
+
callback_object.response_code = response_code
|
70
|
+
callback_object.response_content = response_content
|
65
71
|
|
66
72
|
callback_object.is_sent = True
|
67
73
|
callback_object.save()
|
squad/core/history.py
CHANGED
@@ -3,7 +3,7 @@ from django.core.paginator import Paginator
|
|
3
3
|
|
4
4
|
from squad.core.queries import test_confidence
|
5
5
|
from squad.core.utils import parse_name
|
6
|
-
from squad.core.models import SuiteMetadata, KnownIssue, Environment
|
6
|
+
from squad.core.models import SuiteMetadata, KnownIssue, Environment, Build
|
7
7
|
|
8
8
|
|
9
9
|
class TestResult(object):
|
@@ -49,6 +49,9 @@ class TestHistory(object):
|
|
49
49
|
self.number = page
|
50
50
|
builds = self.paginator.page(page)
|
51
51
|
|
52
|
+
if len(builds) == 0:
|
53
|
+
raise Build.DoesNotExist
|
54
|
+
|
52
55
|
self.top = builds[0]
|
53
56
|
|
54
57
|
issues_by_env = {}
|
@@ -2,6 +2,7 @@ from glob import glob
|
|
2
2
|
import os
|
3
3
|
import re
|
4
4
|
from django.core.management.base import BaseCommand
|
5
|
+
from django.core.files import File
|
5
6
|
|
6
7
|
|
7
8
|
from squad.core.models import Build
|
@@ -123,7 +124,7 @@ class Command(BaseCommand):
|
|
123
124
|
for f in glob(os.path.join(directory, '*')):
|
124
125
|
name = os.path.basename(f)
|
125
126
|
if name not in ['metrics.json', 'metadata.json', 'tests.json']:
|
126
|
-
attachments[name] = open(f, 'rb')
|
127
|
+
attachments[name] = File(open(f, 'rb'))
|
127
128
|
|
128
129
|
if not self.options['silent']:
|
129
130
|
print("Importing test run: %s" % directory)
|
squad/core/models.py
CHANGED
@@ -16,7 +16,7 @@ from django.db.models.query import prefetch_related_objects
|
|
16
16
|
from django.contrib.auth.models import User, AnonymousUser, Group as auth_group
|
17
17
|
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
|
18
18
|
from django.contrib.contenttypes.models import ContentType
|
19
|
-
from django.db.models.signals import post_save, pre_delete
|
19
|
+
from django.db.models.signals import post_save, pre_delete, pre_save
|
20
20
|
from django.dispatch import receiver
|
21
21
|
|
22
22
|
from django.conf import settings
|
@@ -909,8 +909,8 @@ class Attachment(models.Model):
|
|
909
909
|
self.__data__ = b''
|
910
910
|
return self.__data__
|
911
911
|
|
912
|
-
def save_file(self, filename,
|
913
|
-
storage_save(self, self.storage, filename,
|
912
|
+
def save_file(self, filename, file):
|
913
|
+
storage_save(self, self.storage, filename, file)
|
914
914
|
|
915
915
|
|
916
916
|
class SuiteMetadata(models.Model):
|
@@ -1428,9 +1428,13 @@ class ProjectStatus(models.Model, TestSummaryBase):
|
|
1428
1428
|
return None
|
1429
1429
|
|
1430
1430
|
def __get_yaml_field__(self, field_value):
|
1431
|
-
if field_value is
|
1431
|
+
if field_value is None:
|
1432
|
+
return {}
|
1433
|
+
|
1434
|
+
try:
|
1432
1435
|
return yaml.load(field_value, Loader=yaml.Loader)
|
1433
|
-
|
1436
|
+
except yaml.YAMLError:
|
1437
|
+
return {}
|
1434
1438
|
|
1435
1439
|
def get_regressions(self):
|
1436
1440
|
return self.__get_yaml_field__(self.regressions)
|
@@ -1467,6 +1471,14 @@ class ProjectStatus(models.Model, TestSummaryBase):
|
|
1467
1471
|
return thresholds_exceeded
|
1468
1472
|
|
1469
1473
|
|
1474
|
+
@receiver(pre_save, sender=ProjectStatus)
|
1475
|
+
def validate_project_status(sender, instance, *args, **kwargs):
|
1476
|
+
yaml_validator(instance.regressions)
|
1477
|
+
yaml_validator(instance.fixes)
|
1478
|
+
yaml_validator(instance.metric_regressions)
|
1479
|
+
yaml_validator(instance.metric_fixes)
|
1480
|
+
|
1481
|
+
|
1470
1482
|
class NotificationDelivery(models.Model):
|
1471
1483
|
|
1472
1484
|
status = models.ForeignKey('ProjectStatus', related_name='deliveries', on_delete=models.CASCADE)
|
squad/core/tasks/__init__.py
CHANGED
@@ -172,9 +172,9 @@ class ReceiveTestRun(object):
|
|
172
172
|
if log_file is not None:
|
173
173
|
testrun.save_log_file(log_file)
|
174
174
|
|
175
|
-
for filename,
|
176
|
-
attachment = testrun.attachments.create(filename=filename, length=
|
177
|
-
attachment.save_file(filename,
|
175
|
+
for filename, file in attachments.items():
|
176
|
+
attachment = testrun.attachments.create(filename=filename, length=file.size)
|
177
|
+
attachment.save_file(filename, file)
|
178
178
|
|
179
179
|
testrun.refresh_from_db()
|
180
180
|
|
squad/core/utils.py
CHANGED
@@ -67,7 +67,7 @@ def yaml_validator(value):
|
|
67
67
|
if len(value) == 0:
|
68
68
|
return
|
69
69
|
try:
|
70
|
-
if not isinstance(yaml.
|
70
|
+
if not isinstance(yaml.load(value, Loader=yaml.Loader), dict):
|
71
71
|
raise ValidationError("Dictionary object expected")
|
72
72
|
except yaml.YAMLError as e:
|
73
73
|
raise ValidationError(e)
|
@@ -169,8 +169,11 @@ def log_deletion(request, object, message):
|
|
169
169
|
|
170
170
|
|
171
171
|
def storage_save(obj, storage_field, filename, content):
|
172
|
-
content_bytes = content or ''
|
173
|
-
if type(content_bytes) is str:
|
174
|
-
content_bytes = content_bytes.encode()
|
175
172
|
filename = '%s/%s/%s' % (obj.__class__.__name__.lower(), obj.pk, filename)
|
176
|
-
|
173
|
+
if type(content) in [bytes, str]:
|
174
|
+
content_bytes = content or ''
|
175
|
+
if type(content_bytes) is str:
|
176
|
+
content_bytes = content_bytes.encode()
|
177
|
+
storage_field.save(filename, ContentFile(content_bytes))
|
178
|
+
else:
|
179
|
+
storage_field.save(filename, content)
|
squad/frontend/comparison.py
CHANGED
@@ -3,6 +3,7 @@ from functools import reduce
|
|
3
3
|
from django.shortcuts import render, get_object_or_404
|
4
4
|
from django.core.paginator import Paginator
|
5
5
|
from django.db.models import Q, Prefetch
|
6
|
+
from django.http import HttpResponseNotFound
|
6
7
|
|
7
8
|
from squad.core.models import Project, Group, Build
|
8
9
|
from squad.core.comparison import TestComparison, MetricComparison
|
@@ -107,7 +108,11 @@ def compare_builds(request):
|
|
107
108
|
comparison = None
|
108
109
|
project = None
|
109
110
|
if project_slug:
|
110
|
-
|
111
|
+
group_and_project = project_slug.split('/')
|
112
|
+
if len(group_and_project) != 2:
|
113
|
+
return HttpResponseNotFound()
|
114
|
+
|
115
|
+
group_slug, project_slug = group_and_project
|
111
116
|
project = get_object_or_404(Project, group__slug=group_slug, slug=project_slug)
|
112
117
|
|
113
118
|
baseline_build = request.GET.get('baseline')
|
squad/frontend/tests.py
CHANGED
@@ -6,7 +6,7 @@ from django.shortcuts import render, get_object_or_404
|
|
6
6
|
from django.http import Http404
|
7
7
|
|
8
8
|
from squad.http import auth
|
9
|
-
from squad.core.models import Test, Suite, SuiteMetadata, Environment
|
9
|
+
from squad.core.models import Build, Test, Suite, SuiteMetadata, Environment
|
10
10
|
from squad.core.history import TestHistory
|
11
11
|
from squad.core.queries import test_confidence
|
12
12
|
from squad.frontend.views import get_build
|
@@ -234,5 +234,5 @@ def test_history(request, group_slug, project_slug, build_version=None, testrun_
|
|
234
234
|
history = TestHistory(project, full_test_name, top=top, page=page)
|
235
235
|
context.update({"history": history})
|
236
236
|
return render(request, 'squad/test_history.jinja2', context)
|
237
|
-
except (Suite.DoesNotExist, SuiteMetadata.DoesNotExist) as e:
|
237
|
+
except (Suite.DoesNotExist, SuiteMetadata.DoesNotExist, Build.DoesNotExist) as e:
|
238
238
|
raise Http404(f"Test not found: {e}")
|
@@ -0,0 +1 @@
|
|
1
|
+
from squad.plugins import Plugin # noqa
|
@@ -0,0 +1,152 @@
|
|
1
|
+
import hashlib
|
2
|
+
import re
|
3
|
+
from collections import defaultdict
|
4
|
+
|
5
|
+
from django.template.defaultfilters import slugify
|
6
|
+
|
7
|
+
REGEX_NAME = 0
|
8
|
+
REGEX_BODY = 1
|
9
|
+
REGEX_EXTRACT_NAME = 2
|
10
|
+
|
11
|
+
|
12
|
+
class BaseLogParser:
|
13
|
+
def compile_regexes(self, regexes):
|
14
|
+
combined = [r"(%s)" % r[REGEX_BODY] for r in regexes]
|
15
|
+
return re.compile(r"|".join(combined), re.S | re.M)
|
16
|
+
|
17
|
+
def remove_numbers_and_time(self, snippet):
|
18
|
+
without_numbers = re.sub(r"(0x[a-f0-9]+|[<\[][0-9a-f]+?[>\]]|\d+)", "", snippet)
|
19
|
+
without_time = re.sub(r"^\[[^\]]+\]", "", without_numbers)
|
20
|
+
|
21
|
+
return without_time
|
22
|
+
|
23
|
+
def create_name(self, snippet, compiled_regex=None):
|
24
|
+
matches = None
|
25
|
+
if compiled_regex:
|
26
|
+
matches = compiled_regex.findall(snippet)
|
27
|
+
if not matches:
|
28
|
+
# Only extract a name if we provide a regex to extract the name and
|
29
|
+
# there is a match
|
30
|
+
return None
|
31
|
+
snippet = matches[0]
|
32
|
+
without_numbers_and_time = self.remove_numbers_and_time(snippet)
|
33
|
+
|
34
|
+
# Limit the name length to 191 characters, since the max name length
|
35
|
+
# for SuiteMetadata in SQUAD is 256 characters. The SHA and "-" take 65
|
36
|
+
# characters: 256-65=191
|
37
|
+
return slugify(without_numbers_and_time)[:191]
|
38
|
+
|
39
|
+
def create_shasum(self, snippet):
|
40
|
+
sha = hashlib.sha256()
|
41
|
+
without_numbers_and_time = self.remove_numbers_and_time(snippet)
|
42
|
+
sha.update(without_numbers_and_time.encode())
|
43
|
+
return sha.hexdigest()
|
44
|
+
|
45
|
+
def create_name_log_dict(self, test_name, lines, test_regex=None):
|
46
|
+
"""
|
47
|
+
Produce a dictionary with the test names as keys and the extracted logs
|
48
|
+
for that test name as values. There will be at least one test name per
|
49
|
+
regex. If there were any matches for a given regex, then a new test
|
50
|
+
will be generated using test_name + shasum.
|
51
|
+
"""
|
52
|
+
# Run the REGEX_EXTRACT_NAME regex over the log lines to sort them by
|
53
|
+
# extracted name. If no name is extracted or the log parser did not
|
54
|
+
# have any output for a particular regex, just use the default name
|
55
|
+
# (for example "check-kernel-oops").
|
56
|
+
tests_without_shas_to_create = defaultdict(set)
|
57
|
+
tests_with_shas_to_create = defaultdict(set)
|
58
|
+
|
59
|
+
# If there are no lines, use the default name and create a passing
|
60
|
+
# test. For example "check-kernel-oops"
|
61
|
+
if not lines:
|
62
|
+
tests_without_shas_to_create[test_name] = []
|
63
|
+
|
64
|
+
# If there are lines, then create the tests for these.
|
65
|
+
for line in lines:
|
66
|
+
extracted_name = self.create_name(line, test_regex)
|
67
|
+
if extracted_name:
|
68
|
+
extended_test_name = f"{test_name}-{extracted_name}"
|
69
|
+
else:
|
70
|
+
extended_test_name = test_name
|
71
|
+
tests_without_shas_to_create[extended_test_name].add(line)
|
72
|
+
|
73
|
+
for name, test_lines in tests_without_shas_to_create.items():
|
74
|
+
# Some lines of the matched regex might be the same, and we don't want to create
|
75
|
+
# multiple tests like test1-sha1, test1-sha1, etc, so we'll create a set of sha1sums
|
76
|
+
# then create only new tests for unique sha's
|
77
|
+
|
78
|
+
for line in test_lines:
|
79
|
+
sha = self.create_shasum(line)
|
80
|
+
name_with_sha = f"{name}-{sha}"
|
81
|
+
tests_with_shas_to_create[name_with_sha].add(line)
|
82
|
+
|
83
|
+
return tests_without_shas_to_create, tests_with_shas_to_create
|
84
|
+
|
85
|
+
def create_squad_tests_from_name_log_dict(
|
86
|
+
self, suite, testrun, tests_without_shas_to_create, tests_with_shas_to_create
|
87
|
+
):
|
88
|
+
# Import SuiteMetadata from SQUAD only when required so BaseLogParser
|
89
|
+
# does not require a SQUAD to work. This makes it easier to reuse this
|
90
|
+
# class outside of SQUAD for testing and developing log parser
|
91
|
+
# patterns.
|
92
|
+
from squad.core.models import SuiteMetadata
|
93
|
+
|
94
|
+
for name, lines in tests_without_shas_to_create.items():
|
95
|
+
metadata, _ = SuiteMetadata.objects.get_or_create(
|
96
|
+
suite=suite.slug, name=name, kind="test"
|
97
|
+
)
|
98
|
+
testrun.tests.create(
|
99
|
+
suite=suite,
|
100
|
+
result=(len(lines) == 0),
|
101
|
+
log="\n".join(lines),
|
102
|
+
metadata=metadata,
|
103
|
+
build=testrun.build,
|
104
|
+
environment=testrun.environment,
|
105
|
+
)
|
106
|
+
for name_with_sha, lines in tests_with_shas_to_create.items():
|
107
|
+
metadata, _ = SuiteMetadata.objects.get_or_create(
|
108
|
+
suite=suite.slug, name=name_with_sha, kind="test"
|
109
|
+
)
|
110
|
+
testrun.tests.create(
|
111
|
+
suite=suite,
|
112
|
+
result=False,
|
113
|
+
log="\n---\n".join(lines),
|
114
|
+
metadata=metadata,
|
115
|
+
build=testrun.build,
|
116
|
+
environment=testrun.environment,
|
117
|
+
)
|
118
|
+
|
119
|
+
def create_squad_tests(self, testrun, suite, test_name, lines, test_regex=None):
|
120
|
+
"""
|
121
|
+
There will be at least one test per regex. If there were any match for
|
122
|
+
a given regex, then a new test will be generated using test_name +
|
123
|
+
shasum. This helps comparing kernel logs across different builds
|
124
|
+
"""
|
125
|
+
tests_without_shas_to_create, tests_with_shas_to_create = (
|
126
|
+
self.create_name_log_dict(test_name, lines, test_regex)
|
127
|
+
)
|
128
|
+
self.create_squad_tests_from_name_log_dict(
|
129
|
+
suite,
|
130
|
+
testrun,
|
131
|
+
tests_without_shas_to_create,
|
132
|
+
tests_with_shas_to_create,
|
133
|
+
)
|
134
|
+
|
135
|
+
def join_matches(self, matches, regexes):
|
136
|
+
"""
|
137
|
+
group regex in python are returned as a list of tuples which each
|
138
|
+
group match in one of the positions in the tuple. Example:
|
139
|
+
regex = r'(a)|(b)|(c)'
|
140
|
+
matches = [
|
141
|
+
('match a', '', ''),
|
142
|
+
('', 'match b', ''),
|
143
|
+
('match a', '', ''),
|
144
|
+
('', '', 'match c')
|
145
|
+
]
|
146
|
+
"""
|
147
|
+
snippets = {regex_id: [] for regex_id in range(len(regexes))}
|
148
|
+
for match in matches:
|
149
|
+
for regex_id in range(len(regexes)):
|
150
|
+
if len(match[regex_id]) > 0:
|
151
|
+
snippets[regex_id].append(match[regex_id])
|
152
|
+
return snippets
|
@@ -1,40 +1,30 @@
|
|
1
|
-
import hashlib
|
2
1
|
import logging
|
3
2
|
import re
|
4
|
-
from collections import defaultdict
|
5
3
|
from squad.plugins import Plugin as BasePlugin
|
6
|
-
from squad.
|
7
|
-
|
4
|
+
from squad.plugins.lib.base_log_parser import BaseLogParser, REGEX_NAME, REGEX_EXTRACT_NAME
|
8
5
|
|
9
6
|
logger = logging.getLogger()
|
10
7
|
|
11
|
-
REGEX_NAME = 0
|
12
|
-
REGEX_BODY = 1
|
13
|
-
|
14
8
|
MULTILINERS = [
|
15
|
-
('check-kernel-exception', r'-+\[? cut here \]?-+.*?-+\[? end trace \w* \]?-+'),
|
16
|
-
('check-kernel-kasan', r'=+\n\[[\s\.\d]+\]\s+BUG: KASAN:.*?=+'),
|
17
|
-
('check-kernel-kfence', r'=+\n\[[\s\.\d]+\]\s+BUG: KFENCE:.*?=+'),
|
9
|
+
('check-kernel-exception', r'-+\[? cut here \]?-+.*?-+\[? end trace \w* \]?-+', r"\d][^\+\n]*"),
|
10
|
+
('check-kernel-kasan', r'=+\n\[[\s\.\d]+\]\s+BUG: KASAN:.*?=+', r"BUG: KASAN:[^\+\n]*"),
|
11
|
+
('check-kernel-kfence', r'=+\n\[[\s\.\d]+\]\s+BUG: KFENCE:.*?=+', r"BUG: KFENCE:[^\+\n]*"),
|
18
12
|
]
|
19
13
|
|
20
14
|
ONELINERS = [
|
21
|
-
('check-kernel-oops', r'^[^\n]+Oops(?: -|:).*?$'),
|
22
|
-
('check-kernel-fault', r'^[^\n]+Unhandled fault.*?$'),
|
23
|
-
('check-kernel-warning', r'^[^\n]+WARNING:.*?$'),
|
24
|
-
('check-kernel-bug', r'^[^\n]+(?: kernel BUG at|BUG:).*?$'),
|
25
|
-
('check-kernel-invalid-opcode', r'^[^\n]+invalid opcode:.*?$'),
|
26
|
-
('check-kernel-panic', r'Kernel panic - not syncing.*?$'),
|
15
|
+
('check-kernel-oops', r'^[^\n]+Oops(?: -|:).*?$', r"Oops[^\+\n]*"),
|
16
|
+
('check-kernel-fault', r'^[^\n]+Unhandled fault.*?$', r"Unhandled [^\+\n]*"),
|
17
|
+
('check-kernel-warning', r'^[^\n]+WARNING:.*?$', r"WARNING: [^\+\n]*"),
|
18
|
+
('check-kernel-bug', r'^[^\n]+(?: kernel BUG at|BUG:).*?$', r"BUG[^\+\n]*"),
|
19
|
+
('check-kernel-invalid-opcode', r'^[^\n]+invalid opcode:.*?$', r"invalid opcode: [^\+\n]*"),
|
20
|
+
('check-kernel-panic', r'Kernel panic - not syncing.*?$', r"Kernel [^\+\n]*"),
|
27
21
|
]
|
28
22
|
|
29
23
|
# Tip: broader regexes should come first
|
30
24
|
REGEXES = MULTILINERS + ONELINERS
|
31
25
|
|
32
26
|
|
33
|
-
class Plugin(BasePlugin):
|
34
|
-
def __compile_regexes(self, regexes):
|
35
|
-
combined = [r'(%s)' % r[REGEX_BODY] for r in regexes]
|
36
|
-
return re.compile(r'|'.join(combined), re.S | re.M)
|
37
|
-
|
27
|
+
class Plugin(BasePlugin, BaseLogParser):
|
38
28
|
def __cutoff_boot_log(self, log):
|
39
29
|
# Attempt to split the log in " login:"
|
40
30
|
logs = log.split(' login:', 1)
|
@@ -51,68 +41,6 @@ class Plugin(BasePlugin):
|
|
51
41
|
kernel_msgs = re.findall(r'(\[[ \d]+\.[ \d]+\] .*?)$', log, re.S | re.M)
|
52
42
|
return '\n'.join(kernel_msgs)
|
53
43
|
|
54
|
-
def __join_matches(self, matches, regexes):
|
55
|
-
"""
|
56
|
-
group regex in python are returned as a list of tuples which each
|
57
|
-
group match in one of the positions in the tuple. Example:
|
58
|
-
regex = r'(a)|(b)|(c)'
|
59
|
-
matches = [
|
60
|
-
('match a', '', ''),
|
61
|
-
('', 'match b', ''),
|
62
|
-
('match a', '', ''),
|
63
|
-
('', '', 'match c')
|
64
|
-
]
|
65
|
-
"""
|
66
|
-
snippets = {regex_id: [] for regex_id in range(len(regexes))}
|
67
|
-
for match in matches:
|
68
|
-
for regex_id in range(len(regexes)):
|
69
|
-
if len(match[regex_id]) > 0:
|
70
|
-
snippets[regex_id].append(match[regex_id])
|
71
|
-
return snippets
|
72
|
-
|
73
|
-
def __create_tests(self, testrun, suite, test_name, lines):
|
74
|
-
"""
|
75
|
-
There will be at least one test per regex. If there were any match for a given
|
76
|
-
regex, then a new test will be generated using test_name + shasum. This helps
|
77
|
-
comparing kernel logs accross different builds
|
78
|
-
"""
|
79
|
-
metadata, _ = SuiteMetadata.objects.get_or_create(suite=suite.slug, name=test_name, kind='test')
|
80
|
-
testrun.tests.create(
|
81
|
-
suite=suite,
|
82
|
-
result=(len(lines) == 0),
|
83
|
-
log='\n'.join(lines),
|
84
|
-
metadata=metadata,
|
85
|
-
build=testrun.build,
|
86
|
-
environment=testrun.environment,
|
87
|
-
)
|
88
|
-
|
89
|
-
# Some lines of the matched regex might be the same, and we don't want to create
|
90
|
-
# multiple tests like test1-sha1, test1-sha1, etc, so we'll create a set of sha1sums
|
91
|
-
# then create only new tests for unique sha's
|
92
|
-
shas = defaultdict(set)
|
93
|
-
for line in lines:
|
94
|
-
sha = self.__create_shasum(line)
|
95
|
-
shas[sha].add(line)
|
96
|
-
|
97
|
-
for sha, lines in shas.items():
|
98
|
-
name = f'{test_name}-{sha}'
|
99
|
-
metadata, _ = SuiteMetadata.objects.get_or_create(suite=suite.slug, name=name, kind='test')
|
100
|
-
testrun.tests.create(
|
101
|
-
suite=suite,
|
102
|
-
result=False,
|
103
|
-
log='\n---\n'.join(lines),
|
104
|
-
metadata=metadata,
|
105
|
-
build=testrun.build,
|
106
|
-
environment=testrun.environment,
|
107
|
-
)
|
108
|
-
|
109
|
-
def __create_shasum(self, snippet):
|
110
|
-
sha = hashlib.sha256()
|
111
|
-
without_numbers = re.sub(r'(0x[a-f0-9]+|[<\[][0-9a-f]+?[>\]]|\d+)', '', snippet)
|
112
|
-
without_time = re.sub(r'^\[[^\]]+\]', '', without_numbers)
|
113
|
-
sha.update(without_time.encode())
|
114
|
-
return sha.hexdigest()
|
115
|
-
|
116
44
|
def postprocess_testrun(self, testrun):
|
117
45
|
if testrun.log_file is None:
|
118
46
|
return
|
@@ -127,10 +55,14 @@ class Plugin(BasePlugin):
|
|
127
55
|
log = self.__kernel_msgs_only(log)
|
128
56
|
suite, _ = testrun.build.project.suites.get_or_create(slug=f'log-parser-{log_type}')
|
129
57
|
|
130
|
-
regex = self.
|
58
|
+
regex = self.compile_regexes(REGEXES)
|
131
59
|
matches = regex.findall(log)
|
132
|
-
snippets = self.
|
60
|
+
snippets = self.join_matches(matches, REGEXES)
|
133
61
|
|
134
62
|
for regex_id in range(len(REGEXES)):
|
135
63
|
test_name = REGEXES[regex_id][REGEX_NAME]
|
136
|
-
|
64
|
+
regex_pattern = REGEXES[regex_id][REGEX_EXTRACT_NAME]
|
65
|
+
test_name_regex = None
|
66
|
+
if regex_pattern:
|
67
|
+
test_name_regex = re.compile(regex_pattern, re.S | re.M)
|
68
|
+
self.create_squad_tests(testrun, suite, test_name, snippets[regex_id], test_name_regex)
|
squad/settings.py
CHANGED
@@ -37,17 +37,21 @@ if not os.access(DATA_DIR, os.W_OK):
|
|
37
37
|
# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
|
38
38
|
|
39
39
|
# SECURITY WARNING: keep the secret key used in production secret!
|
40
|
-
|
41
|
-
if
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
40
|
+
secret_key = os.getenv('SECRET_KEY', None)
|
41
|
+
if secret_key:
|
42
|
+
SECRET_KEY = secret_key
|
43
|
+
else:
|
44
|
+
secret_key_file = os.getenv('SECRET_KEY_FILE', None)
|
45
|
+
if secret_key_file is None:
|
46
|
+
secret_key_file = os.path.join(DATA_DIR, 'secret.dat')
|
47
|
+
|
48
|
+
if not os.path.exists(secret_key_file):
|
49
|
+
from squad.core.utils import random_key
|
50
|
+
fd = os.open(secret_key_file, os.O_WRONLY | os.O_CREAT, 0o600)
|
51
|
+
with os.fdopen(fd, 'w') as f:
|
52
|
+
f.write(random_key(64))
|
53
|
+
|
54
|
+
SECRET_KEY = open(secret_key_file).read()
|
51
55
|
|
52
56
|
DEBUG = os.getenv('ENV') not in ['production', 'staging']
|
53
57
|
|
squad/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = '1.
|
1
|
+
__version__ = '1.90'
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: squad
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.90
|
4
4
|
Summary: Software Quality Dashboard
|
5
5
|
Home-page: https://github.com/Linaro/squad
|
6
6
|
Author: Antonio Terceiro
|
@@ -12,26 +12,26 @@ Requires-Dist: aiohttp
|
|
12
12
|
Requires-Dist: celery
|
13
13
|
Requires-Dist: cryptography
|
14
14
|
Requires-Dist: coreapi
|
15
|
-
Requires-Dist: django-crispy-forms
|
16
|
-
Requires-Dist: Django
|
15
|
+
Requires-Dist: django-crispy-forms==1.14.0
|
16
|
+
Requires-Dist: Django>=3
|
17
17
|
Requires-Dist: django-allauth
|
18
18
|
Requires-Dist: django-bootstrap3
|
19
19
|
Requires-Dist: django-celery-results
|
20
20
|
Requires-Dist: django-cors-headers
|
21
21
|
Requires-Dist: django-debug-toolbar
|
22
|
-
Requires-Dist: django-simple-history
|
23
|
-
Requires-Dist: django-filter
|
24
|
-
Requires-Dist: djangorestframework
|
25
|
-
Requires-Dist: djangorestframework-filters
|
22
|
+
Requires-Dist: django-simple-history>3.0
|
23
|
+
Requires-Dist: django-filter>=2.0
|
24
|
+
Requires-Dist: djangorestframework>=3.9.2
|
25
|
+
Requires-Dist: djangorestframework-filters>=1.0.0.dev0
|
26
26
|
Requires-Dist: drf-extensions
|
27
27
|
Requires-Dist: future
|
28
28
|
Requires-Dist: gunicorn
|
29
|
-
Requires-Dist: importlib-metadata
|
30
|
-
Requires-Dist: Jinja2
|
29
|
+
Requires-Dist: importlib-metadata>3
|
30
|
+
Requires-Dist: Jinja2==3.0.3
|
31
31
|
Requires-Dist: Markdown
|
32
|
-
Requires-Dist: msgpack
|
32
|
+
Requires-Dist: msgpack>=0.5.0
|
33
33
|
Requires-Dist: python-dateutil
|
34
|
-
Requires-Dist: PyYAML
|
34
|
+
Requires-Dist: PyYAML>=5.1
|
35
35
|
Requires-Dist: PyJWT
|
36
36
|
Requires-Dist: pyzmq
|
37
37
|
Requires-Dist: requests
|
@@ -40,6 +40,7 @@ Requires-Dist: sqlparse
|
|
40
40
|
Requires-Dist: svgwrite
|
41
41
|
Requires-Dist: whitenoise
|
42
42
|
Provides-Extra: postgres
|
43
|
-
Requires-Dist: psycopg2
|
43
|
+
Requires-Dist: psycopg2; extra == "postgres"
|
44
44
|
|
45
45
|
Software Quality Dashboard
|
46
|
+
|
@@ -7,30 +7,30 @@ squad/http.py,sha256=KuIKtpf3yOvf5fwc0T2MR0ul1l4AKxq3b0CLdk6KBhM,3667
|
|
7
7
|
squad/jinja2.py,sha256=OKX-lzNz6qtTZL56HWv4UBMPuBl4WQXv0qFJztGp9zs,2541
|
8
8
|
squad/mail.py,sha256=xH5wuIpD7u1fTN9vNOcbzByojleaffsKwp-9i3BeOD0,390
|
9
9
|
squad/manage.py,sha256=Z-LXT67p0R-IzwJ9fLIAacEZmU0VUjqDOSg7j2ZSxJ4,1437
|
10
|
-
squad/settings.py,sha256=
|
10
|
+
squad/settings.py,sha256=nx_cXfFOf3i5z7bmIzwkRhBgVHWtDBfK40-yo5hQUzw,14663
|
11
11
|
squad/socialaccount.py,sha256=vySqPwQ3qVVpahuJ-Snln8K--yzRL3bw4Nx27AsB39A,789
|
12
12
|
squad/urls.py,sha256=JiEfVW8YlzLPE52c2aHzdn5kVVKK4o22w8h5KOA6QhQ,2776
|
13
|
-
squad/version.py,sha256=
|
13
|
+
squad/version.py,sha256=xCyEItfDbvgP8ttkgjhTpQfmnJCsuxMtkuIMOjALn54,21
|
14
14
|
squad/wsgi.py,sha256=SF8T0cQ0OPVyuYjO5YXBIQzvSXQHV0M2BTmd4gP1rPs,387
|
15
15
|
squad/api/__init__.py,sha256=CJiVakfAlHVN5mIFRVQYZQfuNUhUgWVbsdYTME4tq7U,1349
|
16
16
|
squad/api/apps.py,sha256=Trk72p-iV1uGn0o5mdJn5HARUoHGbfgO49jwXvpkmdQ,141
|
17
17
|
squad/api/ci.py,sha256=ymG-eMKXpJgrVUiZcqJW-dYZQKvm1LkdR3TUMe4OSoM,6943
|
18
18
|
squad/api/data.py,sha256=obKDV0-neEvj5lPF9VED2gy_hpfhGtLJABYvSY38ing,2379
|
19
19
|
squad/api/filters.py,sha256=Zvp8DCJmiNquFWqvfVseEAAMYYPiT95RUjqKdzcqSnw,6917
|
20
|
-
squad/api/rest.py,sha256=
|
20
|
+
squad/api/rest.py,sha256=ZtbK0c1BLPPnsX79XlKFVYONM_VJ0vacWZ2JsdCd4l0,77342
|
21
21
|
squad/api/urls.py,sha256=rmsdaL1uOCVSZ5x1redup9RliICmijaBjRK5ObsTkG8,1343
|
22
22
|
squad/api/utils.py,sha256=Sa8QFId3_oSqD2UOoY3Kuh54LLDLPNMq2sub5ktd6Fs,1160
|
23
|
-
squad/api/views.py,sha256=
|
23
|
+
squad/api/views.py,sha256=WH4c10e7iRmuL5tWDxG4zEFHzvF5hxDpEVvybfvbc_E,3880
|
24
24
|
squad/ci/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
25
25
|
squad/ci/admin.py,sha256=7yB-6F0cvt0NVvzGOTlZCyGPV_YHarmbKJZTTzataT4,2255
|
26
26
|
squad/ci/apps.py,sha256=6OVnzTdJkxdqEJnKWYE9dZgUcc29_T1LrDw41cK4EQk,139
|
27
27
|
squad/ci/exceptions.py,sha256=a1sccygniTYDSQi7FRn_6doapddFFiMf55AwGUh5Y80,227
|
28
28
|
squad/ci/models.py,sha256=Fm-4b3SDgMh9HXzqjOd4iZDRMJ1D9AnZ2cg7i2OR248,16018
|
29
|
-
squad/ci/tasks.py,sha256=
|
29
|
+
squad/ci/tasks.py,sha256=P0NYjLuyUViTpO1jZMuRVREbFDCccrMCZDw5E4pt928,3882
|
30
30
|
squad/ci/utils.py,sha256=38zHpw8xkZDSFlkG-2BwSK6AkcddK9OkN9LXuQ3SHR0,97
|
31
31
|
squad/ci/backend/__init__.py,sha256=yhpotXT9F4IdAOXvGQ3-17eOHAFwoaqf9SnMX17ab30,534
|
32
32
|
squad/ci/backend/fake.py,sha256=9sPKndsGd5GDNPp35v-zfJWXZCbge-yXH3RBQGgTlPk,2340
|
33
|
-
squad/ci/backend/lava.py,sha256=
|
33
|
+
squad/ci/backend/lava.py,sha256=E4QE0XtAiqArzzx3YSv7_2qYUBs4aSw8JOz0AV0z9W8,33877
|
34
34
|
squad/ci/backend/null.py,sha256=0CVylWELIZw3JyzCROB4XXAjgQUi15YjQz5caRfTNBo,5434
|
35
35
|
squad/ci/backend/tuxsuite.py,sha256=-A4p5HpUWnIC-61os0vdJOfAGoO81szoLkSgzyaUt6c,17901
|
36
36
|
squad/ci/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -76,17 +76,17 @@ squad/ci/templatetags/filter_jobs.py,sha256=IkWjfSJSeImWlpHk2Tz_aiqalJvprStl5h2J
|
|
76
76
|
squad/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
77
77
|
squad/core/admin.py,sha256=tFyRu_rhHN-ABJkEApaT4YtRYa4qxNe-8jou2H3Q0P4,7897
|
78
78
|
squad/core/apps.py,sha256=Bl-4Yg0joKjygdifQG0ROIz4m5bHiPqytQ3G82uyzWc,143
|
79
|
-
squad/core/callback.py,sha256=
|
79
|
+
squad/core/callback.py,sha256=tzj5PFQFiambicGikUg4yjYJ9WW6hDbO2e1jRCgAhVQ,3282
|
80
80
|
squad/core/comparison.py,sha256=LR3-Unv0CTmakFCDzF_h8fm2peTJzkv79mQWNau1iwI,24429
|
81
81
|
squad/core/data.py,sha256=2zw56v7iYRTUc7wlhuUNgwIIMmK2w84hi-amR9J7EPU,2236
|
82
82
|
squad/core/failures.py,sha256=X6lJVghM2fOrd-RfuHeLlezW2pt7owDZ8eX-Kn_Qrt0,918
|
83
|
-
squad/core/history.py,sha256=
|
84
|
-
squad/core/models.py,sha256=
|
83
|
+
squad/core/history.py,sha256=QRSIoDOw6R6vUWMtsPMknsHGM7FaCAeuCYqASCayHTk,3541
|
84
|
+
squad/core/models.py,sha256=sXQmgPtl54IZT7rDmJEU3QK6JSPbi0hTUGRsjwL6PIo,60851
|
85
85
|
squad/core/notification.py,sha256=rOpO6F63w7_5l9gQgWBBEk-MFBjp7x_hVzoVIVyDze0,10030
|
86
86
|
squad/core/plugins.py,sha256=FLgyoXXKnPBYEf2MgHup9M017rHuADHivLhgzmx_cJE,6354
|
87
87
|
squad/core/queries.py,sha256=78fhIJZWXIlDryewYAt96beK1VJad66Ufu8cg3dHh4w,7698
|
88
88
|
squad/core/statistics.py,sha256=xyTHuhdBjcJ4AozZESjTzSD3dBmmCDgLpbg5XpeyO_M,1056
|
89
|
-
squad/core/utils.py,sha256=
|
89
|
+
squad/core/utils.py,sha256=HwCq8SsKJHbBUtF4DZt1iWCuWhqZaHRBn--Yh0O_RH4,5018
|
90
90
|
squad/core/locale/django.pot,sha256=XycSJyEaEpozGBS9zu7QTNQbffZC0D9eSJ-AwXaVZx4,2282
|
91
91
|
squad/core/locale/es_MX/LC_MESSAGES/django.po,sha256=bwvTWHK2KOT6zFqbIYh61_xYqRnMaQECZsMsOvNdMNw,3071
|
92
92
|
squad/core/locale/pl/LC_MESSAGES/django.po,sha256=mI-Vo8OKWCcx4PrsoB6GiPY3lYU55tSqh0sO6fUeK2Y,3111
|
@@ -98,7 +98,7 @@ squad/core/management/commands/compute_build_summaries.py,sha256=dz6-3vXtFNGYOzl
|
|
98
98
|
squad/core/management/commands/compute_project_statuses.py,sha256=qcm71zEP_A-XhNWrDHM55TJSgKUk_oWjewuZEu2B2KM,3134
|
99
99
|
squad/core/management/commands/fill_test_metadata.py,sha256=EG2mqKtThY5D7nnGalM3q0XOPEVDiDnFLV7sw7YSz1U,1326
|
100
100
|
squad/core/management/commands/fix_squadplugin_data.py,sha256=cbjPL_-AvazBsmXKd5x6LpaoP-3MGpa3uoUUxljVzdw,5072
|
101
|
-
squad/core/management/commands/import_data.py,sha256=
|
101
|
+
squad/core/management/commands/import_data.py,sha256=KgSTNtrQQiqzqjJdvKDHbU6IExPsdTbdMJ-yqfZY4Y4,4556
|
102
102
|
squad/core/management/commands/import_data.rst,sha256=79tAcJ6hOVRVzW2iheQuO6o2RHZKbbFtsHM-IEr6490,1444
|
103
103
|
squad/core/management/commands/migrate_test_runs.py,sha256=RHV06tb4gWyv_q-ooC821_QGZi0WGwxjIYaUGTboqfI,4214
|
104
104
|
squad/core/management/commands/populate_metric_build_and_environment.py,sha256=DJP9_YLRso0RiERBVsB0GP4-GaiRtJb0rAiUQDfFNQk,3166
|
@@ -277,7 +277,7 @@ squad/core/migrations/0167_add_project_datetime.py,sha256=VUBG-qsAhh2f2NXaHOqfX9
|
|
277
277
|
squad/core/migrations/0168_add_group_settings.py,sha256=5UdylfMMNavTL0KXkjPSiEMhSisGWXbhUXQSzfK29Ck,462
|
278
278
|
squad/core/migrations/0169_userpreferences.py,sha256=FwYv9RWxMWdQ2lXJMgi-Xc6XBB5Kp-_YTAOr9GVq1To,1098
|
279
279
|
squad/core/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
280
|
-
squad/core/tasks/__init__.py,sha256=
|
280
|
+
squad/core/tasks/__init__.py,sha256=pYbEkFzNaat7iQQretRiJQPPF4Sq-5-hBykJYnBM04g,18567
|
281
281
|
squad/core/tasks/exceptions.py,sha256=n4cbmJFBdA6KWsGiTbfN9DyYGbJpk0DjR0UneEYw_W0,931
|
282
282
|
squad/core/tasks/notification.py,sha256=6ZyTbUQZPITPP-4r9MUON7x-NbwvDBG8YeabM6fsjzA,4915
|
283
283
|
squad/core/templates/squad/notification/base.jinja2,sha256=AbtQioEHV5DJBW4Etsu0-DQXd_8tQCnLejzgbDGDW7s,3413
|
@@ -296,7 +296,7 @@ squad/frontend/apps.py,sha256=lKMd_HrIna5OZrfeWXndcGoIDR2KFmBFnMoGHtCGE4E,151
|
|
296
296
|
squad/frontend/badges.py,sha256=rEgjkJQKZQT1mL9j9s47okTQO-J55eU8mDNHShijovY,5878
|
297
297
|
squad/frontend/build_settings.py,sha256=_Hqw5npczuU01Zi6FGAiSbrtMMzK9eAXv-cX5U5btto,909
|
298
298
|
squad/frontend/ci.py,sha256=lfglUArCj5iYRLZgC6mDgEN_k-dDqfCezXW3j2Fn_Uc,2244
|
299
|
-
squad/frontend/comparison.py,sha256=
|
299
|
+
squad/frontend/comparison.py,sha256=Oy3Cffphy5qPsEGVAzICkCszb9JqHwaj-0RNMNxISLA,4721
|
300
300
|
squad/frontend/extract.py,sha256=p88JGuBvaC4AMDkJi7lqzbj5ZGh6h2LSlV7tcXbmxDc,8491
|
301
301
|
squad/frontend/forms.py,sha256=StPdrHsFsEoBKEOF6bBampLXbVXrZcEDkbuI4II1dCA,753
|
302
302
|
squad/frontend/group_settings.py,sha256=mV0kJEfRo41AEbOZMxWXY1MpYNqSgWVqac0dUdwkGyk,6232
|
@@ -304,7 +304,7 @@ squad/frontend/metrics.py,sha256=nGrfFHLG6g_DUYVJCIDlFLvLWhMrqJxX1Z0cckPBJlg,114
|
|
304
304
|
squad/frontend/project_settings.py,sha256=TtWz8h8Goeb3pccLy9jLUibeHqyqkdK8phL7_Vh_d0I,5045
|
305
305
|
squad/frontend/queries.py,sha256=NxQF2woAf9A4Wk_ozHzZXOGmr2as-j7hqfvmsfJ-ojc,967
|
306
306
|
squad/frontend/setup.py,sha256=NF9VunY1HJGB2HsHJss-go7EGmqr__JASddxiBCvmeQ,169
|
307
|
-
squad/frontend/tests.py,sha256=
|
307
|
+
squad/frontend/tests.py,sha256=PidrjaToK_Cks0s9Mc4i3Vh4UXOWoXTZlpnxQ2wWjHY,8740
|
308
308
|
squad/frontend/urls.py,sha256=biWauxwXR5j9kOfrSUqkv1Iqz-elB2aNViS9_UFoLzQ,4882
|
309
309
|
squad/frontend/user_settings.py,sha256=U_i59iuylg98uH98K4ezPa2NY56idslBhn7MS6FguHQ,4976
|
310
310
|
squad/frontend/utils.py,sha256=DeH58CJUI1dovpQrj3a-DcxNzM0cxsnBDOF0mrC4Qws,1364
|
@@ -425,15 +425,17 @@ squad/plugins/__init__.py,sha256=9BSzy2jFIoDpWlhD7odPPrLdW4CC3btBhdFCvB651dM,152
|
|
425
425
|
squad/plugins/example.py,sha256=BKpwd315lHRIuNXJPteibpwfnI6C5eXYHYdFYBtVmsI,89
|
426
426
|
squad/plugins/gerrit.py,sha256=CqO2KnFQzu9utr_TQ-sGr1wg3ln0B-bS2-c0_i8T5-c,7009
|
427
427
|
squad/plugins/github.py,sha256=pdtLZw_7xNuzkaFvY_zWi0f2rsMlalXjKm7sz0eADz4,2429
|
428
|
-
squad/plugins/linux_log_parser.py,sha256=
|
428
|
+
squad/plugins/linux_log_parser.py,sha256=9vjt3z7ZDYofR90-JZv260phKMJyRv3sdksr8satgi0,2662
|
429
|
+
squad/plugins/lib/__init__.py,sha256=jzazbAvp2_ibblAs0cKZrmo9aR2EL3hKLyRDE008r2I,40
|
430
|
+
squad/plugins/lib/base_log_parser.py,sha256=lfGoHWYTzOmWxME-qGArpJatzjUdy0uEII5eokDfP1Y,6189
|
429
431
|
squad/run/__init__.py,sha256=ssE8GPAGFiK6V0WpZYowav6Zqsd63dfDMMYasNa1sQg,1410
|
430
432
|
squad/run/__main__.py,sha256=DOl8JOi4Yg7DdtwnUeGqtYBJ6P2k-D2psAEuYOjWr8w,66
|
431
433
|
squad/run/listener.py,sha256=jBeOQhPGb4EdIREB1QsCzYuumsfJ-TqJPd3nR-0m59g,200
|
432
434
|
squad/run/scheduler.py,sha256=CDJG3q5C0GuQuxwlMOfWTSSJpDdwbR6rzpbJfuA0xuw,277
|
433
435
|
squad/run/worker.py,sha256=jtML0h5qKDuSbpJ6_rpWP4MT_rsGA7a24AhwGxBquzk,594
|
434
|
-
squad-1.
|
435
|
-
squad-1.
|
436
|
-
squad-1.
|
437
|
-
squad-1.
|
438
|
-
squad-1.
|
439
|
-
squad-1.
|
436
|
+
squad-1.90.dist-info/COPYING,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
|
437
|
+
squad-1.90.dist-info/METADATA,sha256=wn7qXz18DRF1uk_WM5Fh_TjO8RxUg-eanVQnfmjbKVk,1271
|
438
|
+
squad-1.90.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
|
439
|
+
squad-1.90.dist-info/entry_points.txt,sha256=J_jG3qnkoOHX4RFNGC0f83eJ4BSvK3pqLFkoF3HWfmA,195
|
440
|
+
squad-1.90.dist-info/top_level.txt,sha256=_x9uqE1XppiiytmVTl_qNgpnXus6Gsef69HqfliE7WI,6
|
441
|
+
squad-1.90.dist-info/RECORD,,
|
File without changes
|
File without changes
|