squad 1.89__py3-none-any.whl → 1.90__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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] = read_file_upload(f)
108
+ attachments[f.name] = f
109
109
  test_run_data['attachments'] = attachments
110
110
 
111
111
  receive = ReceiveTestRun(project)
@@ -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').read()
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
@@ -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, contents):
913
- storage_save(self, self.storage, filename, contents)
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):
@@ -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, data in attachments.items():
176
- attachment = testrun.attachments.create(filename=filename, length=len(data))
177
- attachment.save_file(filename, data)
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
@@ -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
- storage_field.save(filename, ContentFile(content_bytes))
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)
@@ -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,18 +1,10 @@
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.core.models import SuiteMetadata
7
- from django.template.defaultfilters import slugify
8
-
4
+ from squad.plugins.lib.base_log_parser import BaseLogParser, REGEX_NAME, REGEX_EXTRACT_NAME
9
5
 
10
6
  logger = logging.getLogger()
11
7
 
12
- REGEX_NAME = 0
13
- REGEX_BODY = 1
14
- REGEX_EXTRACT_NAME = 2
15
-
16
8
  MULTILINERS = [
17
9
  ('check-kernel-exception', r'-+\[? cut here \]?-+.*?-+\[? end trace \w* \]?-+', r"\d][^\+\n]*"),
18
10
  ('check-kernel-kasan', r'=+\n\[[\s\.\d]+\]\s+BUG: KASAN:.*?=+', r"BUG: KASAN:[^\+\n]*"),
@@ -32,11 +24,7 @@ ONELINERS = [
32
24
  REGEXES = MULTILINERS + ONELINERS
33
25
 
34
26
 
35
- class Plugin(BasePlugin):
36
- def __compile_regexes(self, regexes):
37
- combined = [r'(%s)' % r[REGEX_BODY] for r in regexes]
38
- return re.compile(r'|'.join(combined), re.S | re.M)
39
-
27
+ class Plugin(BasePlugin, BaseLogParser):
40
28
  def __cutoff_boot_log(self, log):
41
29
  # Attempt to split the log in " login:"
42
30
  logs = log.split(' login:', 1)
@@ -53,109 +41,6 @@ class Plugin(BasePlugin):
53
41
  kernel_msgs = re.findall(r'(\[[ \d]+\.[ \d]+\] .*?)$', log, re.S | re.M)
54
42
  return '\n'.join(kernel_msgs)
55
43
 
56
- def __join_matches(self, matches, regexes):
57
- """
58
- group regex in python are returned as a list of tuples which each
59
- group match in one of the positions in the tuple. Example:
60
- regex = r'(a)|(b)|(c)'
61
- matches = [
62
- ('match a', '', ''),
63
- ('', 'match b', ''),
64
- ('match a', '', ''),
65
- ('', '', 'match c')
66
- ]
67
- """
68
- snippets = {regex_id: [] for regex_id in range(len(regexes))}
69
- for match in matches:
70
- for regex_id in range(len(regexes)):
71
- if len(match[regex_id]) > 0:
72
- snippets[regex_id].append(match[regex_id])
73
- return snippets
74
-
75
- def __create_tests(self, testrun, suite, test_name, lines, test_regex=None):
76
- """
77
- There will be at least one test per regex. If there were any match for a given
78
- regex, then a new test will be generated using test_name + shasum. This helps
79
- comparing kernel logs accross different builds
80
- """
81
- # Run the REGEX_EXTRACT_NAME regex over the log lines to sort them by
82
- # extracted name. If no name is extracted or the log parser did not
83
- # have any output for a particular regex, just use the default name
84
- # (for example "check-kernel-oops").
85
- tests_to_create = defaultdict(set)
86
- shas = defaultdict(set)
87
-
88
- # If there are no lines, use the default name and create a passing
89
- # test. For example "check-kernel-oops"
90
- if not lines:
91
- tests_to_create[test_name] = []
92
-
93
- # If there are lines, then create the tests for these.
94
- for line in lines:
95
- extracted_name = self.__create_name(line, test_regex)
96
- if extracted_name:
97
- extended_test_name = f"{test_name}-{extracted_name}"
98
- else:
99
- extended_test_name = test_name
100
- tests_to_create[extended_test_name].add(line)
101
-
102
- for name, lines in tests_to_create.items():
103
- metadata, _ = SuiteMetadata.objects.get_or_create(suite=suite.slug, name=name, kind='test')
104
- testrun.tests.create(
105
- suite=suite,
106
- result=(len(lines) == 0),
107
- log='\n'.join(lines),
108
- metadata=metadata,
109
- build=testrun.build,
110
- environment=testrun.environment,
111
- )
112
-
113
- # Some lines of the matched regex might be the same, and we don't want to create
114
- # multiple tests like test1-sha1, test1-sha1, etc, so we'll create a set of sha1sums
115
- # then create only new tests for unique sha's
116
-
117
- for line in lines:
118
- sha = self.__create_shasum(line)
119
- name_with_sha = f"{name}-{sha}"
120
- shas[name_with_sha].add(line)
121
-
122
- for name_with_sha, lines in shas.items():
123
- metadata, _ = SuiteMetadata.objects.get_or_create(suite=suite.slug, name=name_with_sha, kind='test')
124
- testrun.tests.create(
125
- suite=suite,
126
- result=False,
127
- log='\n---\n'.join(lines),
128
- metadata=metadata,
129
- build=testrun.build,
130
- environment=testrun.environment,
131
- )
132
-
133
- def __remove_numbers_and_time(self, snippet):
134
- without_numbers = re.sub(r"(0x[a-f0-9]+|[<\[][0-9a-f]+?[>\]]|\d+)", "", snippet)
135
- without_time = re.sub(r"^\[[^\]]+\]", "", without_numbers)
136
-
137
- return without_time
138
-
139
- def __create_name(self, snippet, regex=None):
140
- matches = None
141
- if regex:
142
- matches = regex.findall(snippet)
143
- if not matches:
144
- return None
145
- snippet = matches[0]
146
- without_numbers_and_time = self.__remove_numbers_and_time(snippet)
147
-
148
- # Limit the name length to 191 characters, since the max name length
149
- # for SuiteMetadata in SQUAD is 256 characters. The SHA and "-" take 65
150
- # characters: 256-65=191
151
- return slugify(without_numbers_and_time)[:191]
152
-
153
- def __create_shasum(self, snippet):
154
- sha = hashlib.sha256()
155
- without_numbers_and_time = self.__remove_numbers_and_time(snippet)
156
- sha.update(without_numbers_and_time.encode())
157
- return sha.hexdigest()
158
-
159
44
  def postprocess_testrun(self, testrun):
160
45
  if testrun.log_file is None:
161
46
  return
@@ -170,9 +55,9 @@ class Plugin(BasePlugin):
170
55
  log = self.__kernel_msgs_only(log)
171
56
  suite, _ = testrun.build.project.suites.get_or_create(slug=f'log-parser-{log_type}')
172
57
 
173
- regex = self.__compile_regexes(REGEXES)
58
+ regex = self.compile_regexes(REGEXES)
174
59
  matches = regex.findall(log)
175
- snippets = self.__join_matches(matches, REGEXES)
60
+ snippets = self.join_matches(matches, REGEXES)
176
61
 
177
62
  for regex_id in range(len(REGEXES)):
178
63
  test_name = REGEXES[regex_id][REGEX_NAME]
@@ -180,4 +65,4 @@ class Plugin(BasePlugin):
180
65
  test_name_regex = None
181
66
  if regex_pattern:
182
67
  test_name_regex = re.compile(regex_pattern, re.S | re.M)
183
- self.__create_tests(testrun, suite, test_name, snippets[regex_id], test_name_regex)
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
- secret_key_file = os.getenv('SECRET_KEY_FILE', None)
41
- if secret_key_file is None:
42
- secret_key_file = os.path.join(DATA_DIR, 'secret.dat')
43
-
44
- if not os.path.exists(secret_key_file):
45
- from squad.core.utils import random_key
46
- fd = os.open(secret_key_file, os.O_WRONLY | os.O_CREAT, 0o600)
47
- with os.fdopen(fd, 'w') as f:
48
- f.write(random_key(64))
49
-
50
- SECRET_KEY = open(secret_key_file).read()
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.89'
1
+ __version__ = '1.90'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: squad
3
- Version: 1.89
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 ==1.14.0
16
- Requires-Dist: Django >=3
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 >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
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 >3
30
- Requires-Dist: Jinja2 ==3.0.3
29
+ Requires-Dist: importlib-metadata>3
30
+ Requires-Dist: Jinja2==3.0.3
31
31
  Requires-Dist: Markdown
32
- Requires-Dist: msgpack >=0.5.0
32
+ Requires-Dist: msgpack>=0.5.0
33
33
  Requires-Dist: python-dateutil
34
- Requires-Dist: PyYAML >=5.1
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 ; extra == 'postgres'
43
+ Requires-Dist: psycopg2; extra == "postgres"
44
44
 
45
45
  Software Quality Dashboard
46
+
@@ -7,10 +7,10 @@ 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=CRmnXFDrfdspzXGUIlffRcKMQizJ4Wzyyg1cIRu-h-M,14535
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=DHemnt4WEI99t3epDIiaS6j6mPKaFVroxzlr9RNdMCU,21
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
@@ -20,7 +20,7 @@ squad/api/filters.py,sha256=Zvp8DCJmiNquFWqvfVseEAAMYYPiT95RUjqKdzcqSnw,6917
20
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=kuFlbiyZiD0i9jwwmkL3Y22LwJ3bx2oJs28d1g2DPA0,3898
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
@@ -81,12 +81,12 @@ squad/core/comparison.py,sha256=LR3-Unv0CTmakFCDzF_h8fm2peTJzkv79mQWNau1iwI,2442
81
81
  squad/core/data.py,sha256=2zw56v7iYRTUc7wlhuUNgwIIMmK2w84hi-amR9J7EPU,2236
82
82
  squad/core/failures.py,sha256=X6lJVghM2fOrd-RfuHeLlezW2pt7owDZ8eX-Kn_Qrt0,918
83
83
  squad/core/history.py,sha256=QRSIoDOw6R6vUWMtsPMknsHGM7FaCAeuCYqASCayHTk,3541
84
- squad/core/models.py,sha256=jAQpsCo4uNPF2f93LI-_Wfpu5TjUhHFf6LfwXA8grU0,60859
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=jinGlLEnr8nL84OzBvm0VFGt6EzWWR0r4dOabDrbXfM,4908
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=cuQchMuyMj3UhXvpx-pbMmqdDIwUKxyBp26IiCZGamY,4522
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=QoQPjAJhN8Evqb4uaUYdxqGojRTvc572abQH9N1o9y4,18567
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
@@ -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=8GgIM4skM06uZX1d-15nZ0D4lxg5jOp9XYRvMtUtjA0,7195
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.89.dist-info/COPYING,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
435
- squad-1.89.dist-info/METADATA,sha256=Kb687CzcwB57kqLaoR1vJ9H6ESKXgDx5UToU4yOcZFg,1281
436
- squad-1.89.dist-info/WHEEL,sha256=Wyh-_nZ0DJYolHNn1_hMa4lM7uDedD_RGVwbmTjyItk,91
437
- squad-1.89.dist-info/entry_points.txt,sha256=apCDQydHZtvqV334ql6NhTJUAJeZRdtAm0TVcbbAi5Q,194
438
- squad-1.89.dist-info/top_level.txt,sha256=_x9uqE1XppiiytmVTl_qNgpnXus6Gsef69HqfliE7WI,6
439
- squad-1.89.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (71.1.0)
2
+ Generator: bdist_wheel (0.44.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -4,3 +4,4 @@ squad-admin = squad.manage:main
4
4
  squad-listener = squad.run.listener:main
5
5
  squad-scheduler = squad.run.scheduler:main
6
6
  squad-worker = squad.run.worker:main
7
+
File without changes