Skip to content

Commit 54ab97b

Browse files
authored
Hardening from tests (#1459)
* Add vscode config to point to pylint config * Exclude implicit inherit from object * Cache is a class variable * Create cache clear function * Use clear_cache before deleting portfolios * Adjust to new portfolio project selection structure * Clear SQ cache before running SC tests
1 parent 056011a commit 54ab97b

22 files changed

+214
-137
lines changed

.vscode/settings.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,8 @@
33
"connectionId": "LATEST",
44
"projectKey": "okorach_sonar-tools"
55
},
6-
"sonarlint.focusOnNewCode": true
6+
"sonarlint.focusOnNewCode": true,
7+
"pylint.args": [
8+
"[\"--rcfile=conf/pylintrc\"]"
9+
]
710
}

conf/pylintrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ disable=raw-checker-failed,
8787
W0238,
8888
R0911,
8989
C0325,
90-
W1309
90+
W1309,
91+
R0205
9192

9293

9394
# Enable the message, report, category or checker with the given id(s). You can

sonar/app_branches.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,6 @@
3737
from sonar import exceptions, projects, utilities
3838
import sonar.sqobject as sq
3939

40-
_OBJECTS = {}
41-
4240
APIS = {
4341
"search": "api/components/search_projects",
4442
"get": "api/applications/show",
@@ -55,6 +53,8 @@ class ApplicationBranch(Component):
5553
Abstraction of the SonarQube "application branch" concept
5654
"""
5755

56+
_OBJECTS = {}
57+
5858
def __init__(self, app: App, name: str, project_branches: list[Branch], is_main: bool = False) -> None:
5959
"""Don't use this directly, go through the class methods to create Objects"""
6060
super().__init__(endpoint=app.endpoint, key=f"{app.key} BRANCH {name}")
@@ -64,7 +64,7 @@ def __init__(self, app: App, name: str, project_branches: list[Branch], is_main:
6464
self._project_branches = project_branches
6565
self._last_analysis = None
6666
log.debug("Created object %s with uuid %s id %x", str(self), self.uuid(), id(self))
67-
_OBJECTS[self.uuid()] = self
67+
ApplicationBranch._OBJECTS[self.uuid()] = self
6868

6969
@classmethod
7070
def get_object(cls, app: App, branch_name: str) -> ApplicationBranch:
@@ -80,13 +80,13 @@ def get_object(cls, app: App, branch_name: str) -> ApplicationBranch:
8080
if app.endpoint.edition() == "community":
8181
raise exceptions.UnsupportedOperation(_NOT_SUPPORTED)
8282
uu = uuid(app.key, branch_name, app.endpoint.url)
83-
if uu in _OBJECTS:
84-
return _OBJECTS[uu]
83+
if uu in ApplicationBranch._OBJECTS:
84+
return ApplicationBranch._OBJECTS[uu]
8585
app.refresh()
8686
app.branches()
8787
uu = uuid(app.key, branch_name, app.endpoint.url)
88-
if uu in _OBJECTS:
89-
return _OBJECTS[uu]
88+
if uu in ApplicationBranch._OBJECTS:
89+
return ApplicationBranch._OBJECTS[uu]
9090
raise exceptions.ObjectNotFound(app.key, f"Application key '{app.key}' branch '{branch_name}' not found")
9191

9292
@classmethod
@@ -147,7 +147,7 @@ def delete(self) -> bool:
147147
if self.is_main():
148148
log.warning("Can't delete main %s, simply delete the application for that", str(self))
149149
return False
150-
return sq.delete_object(self, APIS["delete"], self.search_params(), _OBJECTS)
150+
return sq.delete_object(self, APIS["delete"], self.search_params(), ApplicationBranch._OBJECTS)
151151

152152
def reload(self, data: types.ApiPayload) -> None:
153153
"""Reloads an App Branch from JSON data coming from Sonar"""

sonar/applications.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
import sonar.utilities as util
4040
from sonar.audit import rules, problem
4141

42-
_OBJECTS = {}
4342
_CLASS_LOCK = Lock()
4443

4544
APIS = {
@@ -59,6 +58,8 @@ class Application(aggr.Aggregation):
5958
Abstraction of the SonarQube "application" concept
6059
"""
6160

61+
_OBJECTS = {}
62+
6263
SEARCH_API = "api/components/search_projects"
6364
SEARCH_KEY_FIELD = "key"
6465
SEARCH_RETURN_FIELD = "components"
@@ -71,7 +72,7 @@ def __init__(self, endpoint: pf.Platform, key: str, name: str) -> None:
7172
self._description = None
7273
self.name = name
7374
log.debug("Created object %s with uuid %s id %x", str(self), self.uuid(), id(self))
74-
_OBJECTS[self.uuid()] = self
75+
Application._OBJECTS[self.uuid()] = self
7576

7677
@classmethod
7778
def get_object(cls, endpoint: pf.Platform, key: str) -> Application:
@@ -86,8 +87,8 @@ def get_object(cls, endpoint: pf.Platform, key: str) -> Application:
8687
"""
8788
check_supported(endpoint)
8889
uu = sq.uuid(key=key, url=endpoint.url)
89-
if uu in _OBJECTS:
90-
return _OBJECTS[uu]
90+
if uu in Application._OBJECTS:
91+
return Application._OBJECTS[uu]
9192
try:
9293
data = json.loads(endpoint.get(APIS["get"], params={"application": key}).text)["application"]
9394
except (ConnectionError, RequestException) as e:
@@ -111,8 +112,8 @@ def load(cls, endpoint: pf.Platform, data: types.ApiPayload) -> Application:
111112
"""
112113
check_supported(endpoint)
113114
uu = sq.uuid(key=data["key"], url=endpoint.url)
114-
if uu in _OBJECTS:
115-
o = _OBJECTS[uu]
115+
if uu in Application._OBJECTS:
116+
o = Application._OBJECTS[uu]
116117
else:
117118
o = cls(endpoint, data["key"], data["name"])
118119
o.reload(data)
@@ -152,7 +153,7 @@ def refresh(self) -> None:
152153
self.reload(json.loads(self.get(APIS["get"], params=self.search_params()).text)["application"])
153154
except (ConnectionError, RequestException) as e:
154155
if e.response.status_code == HTTPStatus.NOT_FOUND:
155-
_OBJECTS.pop(self.uuid(), None)
156+
Application._OBJECTS.pop(self.uuid(), None)
156157
raise exceptions.ObjectNotFound(self.key, f"{str(self)} not found")
157158
log.error("%s while refreshing %s", util.error_msg(e), str(self))
158159
raise
@@ -261,7 +262,7 @@ def delete(self) -> bool:
261262
for branch in self.branches().values():
262263
if not branch.is_main:
263264
ok = ok and branch.delete()
264-
return ok and sq.delete_object(self, "applications/delete", {"application": self.key}, _OBJECTS)
265+
return ok and sq.delete_object(self, "applications/delete", {"application": self.key}, Application._OBJECTS)
265266

266267
def get_filtered_branches(self, filters: dict[str, str]) -> Union[None, dict[str, object]]:
267268
"""Get lists of branches according to the filter"""
@@ -588,11 +589,11 @@ def search_by_name(endpoint: pf.Platform, name: str) -> dict[str, Application]:
588589
"""Searches applications by name. Several apps may match as name does not have to be unique"""
589590
get_list(endpoint=endpoint, use_cache=False)
590591
data = {}
591-
for app in _OBJECTS.values():
592+
for app in Application._OBJECTS.values():
592593
if app.name == name:
593594
log.debug("Found APP %s id %x", app.key, id(app))
594595
data[app.key] = app
595-
# return {app.key: app for app in _OBJECTS.values() if app.name == name}
596+
# return {app.key: app for app in Application._OBJECTS.values() if app.name == name}
596597
return data
597598

598599

sonar/devops.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,6 @@
3535
#: DevOps platform types in SonarQube
3636
DEVOPS_PLATFORM_TYPES = ("github", "azure", "bitbucket", "bitbucketcloud", "gitlab")
3737

38-
39-
_OBJECTS = {}
40-
4138
_CREATE_API_GITHUB = "alm_settings/create_github"
4239
_CREATE_API_GITLAB = "alm_settings/create_gitlab"
4340
_CREATE_API_AZURE = "alm_settings/create_azure"
@@ -54,21 +51,23 @@ class DevopsPlatform(sq.SqObject):
5451
Abstraction of the SonarQube ALM/DevOps Platform concept
5552
"""
5653

54+
_OBJECTS = {}
55+
5756
def __init__(self, endpoint: platform.Platform, key: str, platform_type: str) -> None:
5857
"""Constructor"""
5958
super().__init__(endpoint=endpoint, key=key)
6059
self.type = platform_type #: DevOps platform type
6160
self.url = None #: DevOps platform URL
6261
self._specific = None #: DevOps platform specific settings
63-
_OBJECTS[self.uuid()] = self
62+
DevopsPlatform._OBJECTS[self.uuid()] = self
6463
log.debug("Created object %s", str(self))
6564

6665
@classmethod
6766
def read(cls, endpoint: platform.Platform, key: str) -> DevopsPlatform:
6867
"""Reads a devops platform object in Sonar instance"""
6968
uu = sq.uuid(key, endpoint.url)
70-
if uu in _OBJECTS:
71-
return _OBJECTS[uu]
69+
if uu in DevopsPlatform._OBJECTS:
70+
return DevopsPlatform._OBJECTS[uu]
7271
data = json.loads(endpoint.get(APIS["list"]).text)
7372
for plt_type, platforms in data.items():
7473
for p in platforms:
@@ -81,8 +80,8 @@ def load(cls, endpoint: platform.Platform, plt_type: str, data: types.ApiPayload
8180
"""Finds a devops platform object and loads it with data"""
8281
key = data["key"]
8382
uu = sq.uuid(key, endpoint.url)
84-
if uu in _OBJECTS:
85-
return _OBJECTS[uu]
83+
if uu in DevopsPlatform._OBJECTS:
84+
return DevopsPlatform._OBJECTS[uu]
8685
o = DevopsPlatform(endpoint=endpoint, key=key, platform_type=plt_type)
8786
return o._load(data)
8887

@@ -198,9 +197,9 @@ def count(platf_type: str = None) -> int:
198197
:rtype: int
199198
"""
200199
if platf_type is None:
201-
return len(_OBJECTS)
200+
return len(DevopsPlatform._OBJECTS)
202201
# Hack: check first 5 chars to that bitbucket cloud and bitbucket server match
203-
return sum(1 for o in _OBJECTS.values() if o.type[0:4] == platf_type[0:4])
202+
return sum(1 for o in DevopsPlatform._OBJECTS.values() if o.type[0:4] == platf_type[0:4])
204203

205204

206205
def get_list(endpoint: platform.Platform) -> dict[str, DevopsPlatform]:
@@ -213,12 +212,12 @@ def get_list(endpoint: platform.Platform) -> dict[str, DevopsPlatform]:
213212
if endpoint.is_sonarcloud():
214213
raise exceptions.UnsupportedOperation("Can't get list of DevOps platforms on SonarCloud")
215214
if endpoint.edition() == "community":
216-
return _OBJECTS
215+
return DevopsPlatform._OBJECTS
217216
data = json.loads(endpoint.get(APIS["list"]).text)
218217
for alm_type in DEVOPS_PLATFORM_TYPES:
219218
for alm_data in data.get(alm_type, {}):
220219
DevopsPlatform.load(endpoint, alm_type, alm_data)
221-
return _OBJECTS
220+
return DevopsPlatform._OBJECTS
222221

223222

224223
def get_object(devops_platform_key: str, endpoint: platform.Platform) -> DevopsPlatform:
@@ -228,7 +227,7 @@ def get_object(devops_platform_key: str, endpoint: platform.Platform) -> DevopsP
228227
:return: The DevOps platforms corresponding to key, or None if not found
229228
:rtype: DevopsPlatform
230229
"""
231-
if len(_OBJECTS) == 0:
230+
if len(DevopsPlatform._OBJECTS) == 0:
232231
get_list(endpoint)
233232
return DevopsPlatform.read(endpoint, devops_platform_key)
234233

@@ -265,7 +264,7 @@ def import_config(endpoint: platform.Platform, config_data: types.ObjectJsonRepr
265264
if endpoint.is_sonarcloud():
266265
raise exceptions.UnsupportedOperation("Can't get import DevOps platforms in SonarCloud")
267266
log.info("Importing DevOps config %s", util.json_dump(devops_settings))
268-
if len(_OBJECTS) == 0:
267+
if len(DevopsPlatform._OBJECTS) == 0:
269268
get_list(endpoint)
270269
for name, data in devops_settings.items():
271270
try:

sonar/groups.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,14 @@
4040
REMOVE_USER_API = "user_groups/remove_user"
4141
_UPDATE_API_V2 = "v2/authorizations/groups"
4242

43-
_OBJECTS = {}
44-
4543

4644
class Group(sq.SqObject):
4745
"""
4846
Abstraction of the SonarQube "group" concept.
4947
Objects of this class must be created with one of the 3 available class methods. Don't use __init__
5048
"""
5149

50+
_OBJECTS = {}
5251
SEARCH_API = "user_groups/search"
5352
SEARCH_API_V2 = "v2/authorizations/groups"
5453
SEARCH_KEY_FIELD = "name"
@@ -63,7 +62,7 @@ def __init__(self, endpoint: pf.Platform, name: str, data: types.ApiPayload) ->
6362
self.__is_default = data.get("default", None)
6463
self._id = data.get("id", None) #: SonarQube 10.4+ Group id
6564
self._json = data
66-
_OBJECTS[self.uuid()] = self
65+
Group._OBJECTS[self.uuid()] = self
6766
log.debug("Created %s object", str(self))
6867

6968
@classmethod
@@ -76,15 +75,15 @@ def read(cls, endpoint: pf.Platform, name: str) -> Group:
7675
"""
7776
log.debug("Reading group '%s'", name)
7877
uid = sq.uuid(name, endpoint.url)
79-
if uid in _OBJECTS:
80-
return _OBJECTS[uid]
78+
if uid in Group._OBJECTS:
79+
return Group._OBJECTS[uid]
8180
data = util.search_by_name(endpoint, name, Group.SEARCH_API, "groups")
8281
if data is None:
8382
raise exceptions.UnsupportedOperation(f"Group '{name}' not found.")
8483
# SonarQube 10 compatibility: "id" field is dropped, use "name" instead
8584
uid = sq.uuid(data.get("id", data["name"]), endpoint.url)
86-
if uid in _OBJECTS:
87-
return _OBJECTS[uid]
85+
if uid in Group._OBJECTS:
86+
return Group._OBJECTS[uid]
8887
return cls(endpoint, name, data=data)
8988

9089
@classmethod
@@ -239,9 +238,9 @@ def set_name(self, name: str) -> bool:
239238
else:
240239
r = self.post(_UPDATE_API, params={"currentName": self.key, "name": name})
241240
if r.ok:
242-
_OBJECTS.pop(self.uuid(), None)
241+
Group._OBJECTS.pop(self.uuid(), None)
243242
self.name = name
244-
_OBJECTS[self.uuid()] = self
243+
Group._OBJECTS[self.uuid()] = self
245244
return r.ok
246245

247246

@@ -316,20 +315,20 @@ def get_object(endpoint: pf.Platform, name: str) -> Group:
316315
:return: The group
317316
"""
318317
uid = sq.uuid(name, endpoint.url)
319-
if len(_OBJECTS) == 0 or uid not in _OBJECTS:
318+
if len(Group._OBJECTS) == 0 or uid not in Group._OBJECTS:
320319
get_list(endpoint)
321-
if uid not in _OBJECTS:
320+
if uid not in Group._OBJECTS:
322321
raise exceptions.ObjectNotFound(name, message=f"Group '{name}' not found")
323-
return _OBJECTS[uid]
322+
return Group._OBJECTS[uid]
324323

325324

326325
def get_object_from_id(endpoint: pf.Platform, id: str) -> Group:
327326
"""Searches a Group object from its id - SonarQube 10.4+"""
328327
if endpoint.version() < (10, 4, 0):
329328
raise exceptions.UnsupportedOperation
330-
if len(_OBJECTS) == 0:
329+
if len(Group._OBJECTS) == 0:
331330
get_list(endpoint)
332-
for o in _OBJECTS.values():
331+
for o in Group._OBJECTS.values():
333332
if o._id == id:
334333
return o
335334
raise exceptions.ObjectNotFound(id, message=f"Group '{id}' not found")

sonar/hotspots.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,6 @@
7373
# Filters for search of hotspots are different than for issues :-(
7474
_FILTERS_HOTSPOTS_REMAPPING = {"resolutions": "resolution", "statuses": "status", "componentsKey": PROJECT_FILTER, "components": PROJECT_FILTER}
7575

76-
_OBJECTS = {}
77-
7876

7977
class TooManyHotspotsError(Exception):
8078
def __init__(self, nbr_issues, message):
@@ -86,6 +84,7 @@ def __init__(self, nbr_issues, message):
8684
class Hotspot(findings.Finding):
8785
"""Abstraction of the Sonar hotspot concept"""
8886

87+
_OBJECTS = {}
8988
SEARCH_API = "hotspots/search"
9089
MAX_PAGE_SIZE = 500
9190
MAX_SEARCH = 10000
@@ -109,7 +108,7 @@ def __init__(self, endpoint: pf.Platform, key: str, data: types.ApiPayload = Non
109108
if m:
110109
self.projectKey = m.group(1)
111110
self.branch = m.group(2)
112-
_OBJECTS[self.uuid()] = self
111+
Hotspot._OBJECTS[self.uuid()] = self
113112
if self.rule is None and self.refresh():
114113
self.rule = self.__details["rule"]["key"]
115114

@@ -437,9 +436,9 @@ def search(endpoint: pf.Platform, filters: types.ApiParams = None) -> dict[str,
437436
def get_object(endpoint: pf.Platform, key: str, data: dict[str] = None, from_export: bool = False) -> Hotspot:
438437
"""Returns a hotspot from its key"""
439438
uid = uuid(key, endpoint.url)
440-
if uid not in _OBJECTS:
439+
if uid not in Hotspot._OBJECTS:
441440
_ = Hotspot(key=key, data=data, endpoint=endpoint, from_export=from_export)
442-
return _OBJECTS[uid]
441+
return Hotspot._OBJECTS[uid]
443442

444443

445444
def sanitize_search_filters(endpoint: pf.Platform, params: types.ApiParams) -> types.ApiParams:

0 commit comments

Comments
 (0)