Skip to content

Commit 036bc2f

Browse files
authored
Revive and clean up repocleaner tests (#2468)
* Update the tests * Revive tests - Fix the tests - Put common code in a common place - Move tests related files in `tests` * Update README.md * extract the ccdb-test url to test_utils organize imports move tests data to tests directory * Update requirements.txt
1 parent 1cda518 commit 036bc2f

15 files changed

+239
-318
lines changed

Framework/script/RepoCleaner/README.md

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,26 @@ There can be any number of these rules. The order is important as we use the fir
3333

3434
The configuration for ccdb-test is described [here](../../../doc/DevelopersTips.md).
3535

36+
## Setup virtual environment for development and test (venv)
37+
38+
1. cd Framework/script/RepoCleaner
39+
2. python3 -m venv env
40+
3. source env/bin/activate
41+
4. python -m pip install -r requirements.txt
42+
5. python3 -m pip install .
43+
6. You can execute and work. Next time just do "activate" and then you are good to go
44+
3645
## Unit Tests
37-
`cd QualityControl/Framework/script/RepoCleaner ; python3 -m unittest discover`
3846

39-
and to test only one of them: `python3 -m unittest tests/test_NewProduction.py -k test_2_runs`
47+
```
48+
cd Framework/script/RepoCleaner
49+
source env/bin/activate
50+
51+
# Run a test:
52+
python -m unittest tests.test_Ccdb.TestCcdb.test_getObjectsList
53+
```
54+
55+
`cd QualityControl/Framework/script/RepoCleaner ; python3 -m unittest discover`
4056

4157
In particular there is a test for the `production` rule that is pretty extensive. It hits the ccdb though and it needs the following path to be truncated:
4258
`
@@ -75,11 +91,3 @@ Create new version
7591
2. `python3 setup.py sdist bdist_wheel`
7692
3. `python3 -m twine upload --repository pypi dist/*`
7793

78-
## Use venv
79-
80-
1. cd Framework/script/RepoCleaner
81-
2. python3 -m venv env
82-
3. source env/bin/activate
83-
4. python -m pip install -r requirements.txt
84-
5. python3 -m pip install .
85-
6. You can execute and work. Next time just do "activate" and then you are good to go

Framework/script/RepoCleaner/qcrepocleaner/Ccdb.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ def __init__(self, path: str, validFrom, validTo, createdAt, uuid=None, metadata
2626
:param uuid: unique id of the object
2727
:param validFrom: validity range smaller limit (in ms)
2828
:param validTo: validity range bigger limit (in ms)
29+
:param createdAt: creation timestamp of the object
2930
'''
3031
self.path = path
3132
self.uuid = uuid
@@ -72,7 +73,8 @@ def getObjectsList(self, added_since: int = 0, path: str = "", no_wildcard: bool
7273
:return A list of strings, each containing a path to an object in the CCDB.
7374
'''
7475
url_for_all_obj = self.url + '/latest/' + path
75-
url_for_all_obj += '/' if no_wildcard else '/.*'
76+
url_for_all_obj += '/' if path else ''
77+
url_for_all_obj += '' if no_wildcard else '.*'
7678
logger.debug(f"Ccdb::getObjectsList -> {url_for_all_obj}")
7779
headers = {'Accept': 'application/json', 'If-Not-Before':str(added_since)}
7880
r = requests.get(url_for_all_obj, headers=headers)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
certifi==2024.2.2
2+
chardet==5.2.0
3+
charset-normalizer==3.3.2
4+
dryable==1.2.0
5+
idna==3.7
6+
psutil==6.1.0
7+
python-consul==1.1.0
8+
PyYAML==6.0.1
9+
requests==2.31.0
10+
responses==0.25.0
11+
six==1.16.0
12+
urllib3==2.2.1

Framework/script/RepoCleaner/qcrepocleaner/config-test.yaml renamed to Framework/script/RepoCleaner/tests/config-test.yaml

File renamed without changes.

Framework/script/RepoCleaner/qcrepocleaner/objectsList.json renamed to Framework/script/RepoCleaner/tests/objectsList.json

File renamed without changes.

Framework/script/RepoCleaner/tests/test_1_per_hour.py

Lines changed: 25 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,12 @@
11
import logging
22
import time
33
import unittest
4-
from datetime import timedelta, date, datetime
5-
6-
from Ccdb import Ccdb, ObjectVersion
7-
from rules import last_only
8-
import os
9-
import sys
10-
import importlib
11-
12-
def import_path(path): # needed because o2-qc-repo-cleaner has no suffix
13-
module_name = os.path.basename(path).replace('-', '_')
14-
spec = importlib.util.spec_from_loader(
15-
module_name,
16-
importlib.machinery.SourceFileLoader(module_name, path)
17-
)
18-
module = importlib.util.module_from_spec(spec)
19-
spec.loader.exec_module(module)
20-
sys.modules[module_name] = module
21-
return module
22-
23-
one_per_hour = import_path("../qcrepocleaner/rules/1_per_hour.py")
4+
from importlib import import_module
5+
from qcrepocleaner.Ccdb import Ccdb
6+
from tests import test_utils
7+
from tests.test_utils import CCDB_TEST_URL
8+
9+
one_per_hour = import_module(".1_per_hour", "qcrepocleaner.rules") # file names should not start with a number...
2410

2511
class Test1PerHour(unittest.TestCase):
2612
"""
@@ -35,86 +21,64 @@ class Test1PerHour(unittest.TestCase):
3521
one_minute = 60000
3622

3723
def setUp(self):
38-
self.ccdb = Ccdb('http://ccdb-test.cern.ch:8080')
24+
self.ccdb = Ccdb(CCDB_TEST_URL) # ccdb-test but please use IP to avoid DNS alerts
3925
self.path = "qc/TST/MO/repo/test"
4026
self.run = 124321
4127
self.extra = {}
4228

4329

4430
def test_1_per_hour(self):
4531
"""
46-
60 versions, 2 minutes apart
32+
120 versions
4733
grace period of 15 minutes
48-
First version is preserved (always). 7 are preserved during the grace period at the end.
49-
One more is preserved after 1 hour. --> 9 preserved
34+
First version is preserved (always). 14 are preserved during the grace period at the end.
35+
One more is preserved after 1 hour. --> 16 preserved
5036
"""
5137
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s',
5238
datefmt='%d-%b-%y %H:%M:%S')
5339
logging.getLogger().setLevel(int(10))
5440

5541
# Prepare data
5642
test_path = self.path + "/test_1_per_hour"
57-
self.prepare_data(test_path, 60, 2)
43+
test_utils.clean_data(self.ccdb, test_path)
44+
test_utils.prepare_data(self.ccdb, test_path, [120], [0], 123)
5845

5946
stats = one_per_hour.process(self.ccdb, test_path, 15, 1, self.in_ten_years, self.extra)
60-
self.assertEqual(stats["deleted"], 51)
61-
self.assertEqual(stats["preserved"], 9)
47+
logging.info(stats)
48+
self.assertEqual(stats["deleted"], 104)
49+
self.assertEqual(stats["preserved"], 16)
6250

6351
objects_versions = self.ccdb.getVersionsList(test_path)
64-
self.assertEqual(len(objects_versions), 9)
52+
self.assertEqual(len(objects_versions), 16)
6553

6654

6755
def test_1_per_hour_period(self):
6856
"""
69-
60 versions, 2 minutes apart
57+
120 versions
7058
no grace period
7159
period of acceptance: 1 hour in the middle
72-
We have therefore 30 versions in the acceptance period.
60+
We have therefore 60 versions in the acceptance period.
7361
Only 1 of them, the one 1 hour after the first version in the set, will be preserved, the others are deleted.
74-
Thus we have 29 deletion. Everything outside the acceptance period is kept.
62+
Thus we have 59 deletion. Everything outside the acceptance period is kept.
7563
"""
7664
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s',
7765
datefmt='%d-%b-%y %H:%M:%S')
7866
logging.getLogger().setLevel(int(10))
7967

8068
# Prepare data
8169
test_path = self.path + "/test_1_per_hour_period"
82-
self.prepare_data(test_path, 60, 2)
70+
test_utils.clean_data(self.ccdb, test_path)
71+
test_utils.prepare_data(self.ccdb, test_path, [120], [0], 123)
8372
current_timestamp = int(time.time() * 1000)
84-
logging.debug(f"{current_timestamp} - {datetime.today()}")
85-
86-
objects_versions = self.ccdb.getVersionsList(test_path)
87-
created = len(objects_versions)
8873

8974
stats = one_per_hour.process(self.ccdb, test_path, 15, current_timestamp-90*60*1000,
9075
current_timestamp-30*60*1000, self.extra)
91-
self.assertEqual(stats["deleted"], 29)
92-
self.assertEqual(stats["preserved"], 31)
76+
logging.info(stats)
77+
self.assertEqual(stats["deleted"], 59)
78+
self.assertEqual(stats["preserved"], 61)
9379

9480
objects_versions = self.ccdb.getVersionsList(test_path)
95-
self.assertEqual(len(objects_versions), 31)
96-
97-
98-
def prepare_data(self, path, number_versions, minutes_between):
99-
"""
100-
Prepare a data set starting `since_minutes` in the past.
101-
1 version per minute
102-
"""
103-
104-
current_timestamp = int(time.time() * 1000)
105-
data = {'part': 'part'}
106-
run = 1234
107-
counter = 0
108-
109-
for x in range(number_versions+1):
110-
counter = counter + 1
111-
from_ts = current_timestamp - minutes_between * x * 60 * 1000
112-
to_ts = current_timestamp
113-
metadata = {'RunNumber': str(run)}
114-
version_info = ObjectVersion(path=path, validFrom=from_ts, validTo=to_ts, metadata=metadata)
115-
self.ccdb.putVersion(version=version_info, data=data)
116-
117-
logging.debug(f"counter : {counter}")
81+
self.assertEqual(len(objects_versions), 61)
11882

11983

12084
if __name__ == '__main__':
Lines changed: 12 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,13 @@
11
import logging
22
import time
33
import unittest
4-
from datetime import timedelta, date, datetime
4+
from importlib import import_module
55

6-
from Ccdb import Ccdb, ObjectVersion
7-
from rules import last_only
8-
import os
9-
import sys
10-
import importlib
11-
12-
13-
def import_path(path): # needed because o2-qc-repo-cleaner has no suffix
14-
module_name = os.path.basename(path).replace('-', '_')
15-
spec = importlib.util.spec_from_loader(
16-
module_name,
17-
importlib.machinery.SourceFileLoader(module_name, path)
18-
)
19-
module = importlib.util.module_from_spec(spec)
20-
spec.loader.exec_module(module)
21-
sys.modules[module_name] = module
22-
return module
23-
24-
25-
one_per_run = import_path("../qcrepocleaner/rules/1_per_run.py")
6+
from qcrepocleaner.Ccdb import Ccdb
7+
from tests import test_utils
8+
from tests.test_utils import CCDB_TEST_URL
269

10+
one_per_run = import_module(".1_per_run", "qcrepocleaner.rules") # file names should not start with a number...
2711

2812
class Test1PerRun(unittest.TestCase):
2913
"""
@@ -38,15 +22,14 @@ class Test1PerRun(unittest.TestCase):
3822
one_minute = 60000
3923

4024
def setUp(self):
41-
self.ccdb = Ccdb('http://ccdb-test.cern.ch:8080')
25+
self.ccdb = Ccdb(CCDB_TEST_URL)
4226
self.path = "qc/TST/MO/repo/test"
4327
self.run = 124321
4428
self.extra = {}
4529

4630
def test_1_per_run(self):
4731
"""
48-
60 versions, 1 minute apart
49-
6 runs
32+
6 runs of 10 versions, versions 1 minute apart
5033
grace period of 15 minutes
5134
Preserved: 14 at the end (grace period), 6 for the runs, but 2 are in both sets --> 14+6-2=18 preserved
5235
"""
@@ -56,7 +39,8 @@ def test_1_per_run(self):
5639

5740
# Prepare data
5841
test_path = self.path + "/test_1_per_run"
59-
self.prepare_data(test_path, 60)
42+
test_utils.clean_data(self.ccdb, test_path)
43+
test_utils.prepare_data(self.ccdb, test_path, [10, 10, 10, 10, 10, 10], [0, 0, 0, 0, 0, 0], 123)
6044

6145
objects_versions = self.ccdb.getVersionsList(test_path)
6246
created = len(objects_versions)
@@ -71,8 +55,7 @@ def test_1_per_run(self):
7155

7256
def test_1_per_run_period(self):
7357
"""
74-
60 versions 1 minute apart
75-
6 runs
58+
6 runs of 10 versions each, versions 1 minute apart
7659
no grace period
7760
acceptance period is only the 38 minutes in the middle
7861
preserved: 6 runs + 11 first and 11 last, with an overlap of 2 --> 26
@@ -83,7 +66,8 @@ def test_1_per_run_period(self):
8366

8467
# Prepare data
8568
test_path = self.path + "/test_1_per_run_period"
86-
self.prepare_data(test_path, 60)
69+
test_utils.clean_data(self.ccdb, test_path)
70+
test_utils.prepare_data(self.ccdb, test_path, [10, 10, 10, 10, 10, 10], [0, 0, 0, 0, 0, 0], 123)
8771
current_timestamp = int(time.time() * 1000)
8872

8973
stats = one_per_run.process(self.ccdb, test_path, 0, current_timestamp - 49 * 60 * 1000,
@@ -94,29 +78,5 @@ def test_1_per_run_period(self):
9478
objects_versions = self.ccdb.getVersionsList(test_path)
9579
self.assertEqual(len(objects_versions), 26)
9680

97-
def prepare_data(self, path, since_minutes):
98-
"""
99-
Prepare a data set starting `since_minutes` in the past.
100-
1 version per minute, 1 run every 10 versions
101-
"""
102-
103-
current_timestamp = int(time.time() * 1000)
104-
data = {'part': 'part'}
105-
run = 1234
106-
counter = 0
107-
108-
for x in range(since_minutes + 1):
109-
counter = counter + 1
110-
from_ts = current_timestamp - x * 60 * 1000
111-
to_ts = current_timestamp
112-
metadata = {'RunNumber': str(run)}
113-
version_info = ObjectVersion(path=path, validFrom=from_ts, validTo=to_ts, metadata=metadata)
114-
self.ccdb.putVersion(version=version_info, data=data)
115-
if x % 10 == 0:
116-
run = run + 1
117-
118-
logging.debug(f"counter : {counter}")
119-
120-
12181
if __name__ == '__main__':
12282
unittest.main()
Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,48 @@
11
import logging
22
import unittest
3-
import requests
3+
from typing import List
4+
45
import responses
56

6-
from Ccdb import Ccdb, ObjectVersion
7-
from rules import production
7+
from qcrepocleaner.Ccdb import Ccdb, ObjectVersion
8+
from tests.test_utils import CCDB_TEST_URL
9+
810

911
class TestCcdb(unittest.TestCase):
1012

1113
def setUp(self):
12-
with open('../qcrepocleaner/objectsList.json') as f: # will close() when we leave this block
14+
with open('objectsList.json') as f: # will close() when we leave this block
1315
self.content_objectslist = f.read()
14-
with open('../versionsList.json') as f: # will close() when we leave this block
16+
with open('versionsList.json') as f: # will close() when we leave this block
1517
self.content_versionslist = f.read()
16-
self.ccdb = Ccdb('http://ccdb-test.cern.ch:8080')
18+
self.ccdb = Ccdb(CCDB_TEST_URL)
19+
logging.getLogger().setLevel(logging.DEBUG)
1720

1821
@responses.activate
1922
def test_getObjectsList(self):
2023
# Prepare mock response
21-
responses.add(responses.GET, 'http://ccdb-test.cern.ch:8080/latest/.*',
24+
responses.add(responses.GET, CCDB_TEST_URL + '/latest/.*',
2225
self.content_objectslist, status=200)
2326
# get list of objects
24-
objectsList = self.ccdb.getObjectsList()
25-
print(f"{objectsList}")
26-
self.assertEqual(len(objectsList), 3)
27-
self.assertEqual(objectsList[0], 'Test')
28-
self.assertEqual(objectsList[1], 'ITSQcTask/ChipStaveCheck')
27+
objects_list = self.ccdb.getObjectsList()
28+
print(f"{objects_list}")
29+
self.assertEqual(len(objects_list), 3)
30+
self.assertEqual(objects_list[0], 'Test')
31+
self.assertEqual(objects_list[1], 'ITSQcTask/ChipStaveCheck')
2932

3033
@responses.activate
3134
def test_getVersionsList(self):
3235
# Prepare mock response
3336
object_path='asdfasdf/example'
34-
responses.add(responses.GET, 'http://ccdb-test.cern.ch:8080/browse/'+object_path,
37+
responses.add(responses.GET, CCDB_TEST_URL + '/browse/'+object_path,
3538
self.content_versionslist, status=200)
3639
# get versions for object
37-
versionsList: List[ObjectVersion] = self.ccdb.getVersionsList(object_path)
38-
print(f"{versionsList}")
39-
self.assertEqual(len(versionsList), 2)
40-
self.assertEqual(versionsList[0].path, object_path)
41-
self.assertEqual(versionsList[1].path, object_path)
42-
self.assertEqual(versionsList[1].metadata["custom"], "34")
40+
versions_list: List[ObjectVersion] = self.ccdb.getVersionsList(object_path)
41+
print(f"{versions_list}")
42+
self.assertEqual(len(versions_list), 2)
43+
self.assertEqual(versions_list[0].path, object_path)
44+
self.assertEqual(versions_list[1].path, object_path)
45+
self.assertEqual(versions_list[1].metadata["custom"], "34")
4346

4447
if __name__ == '__main__':
4548
unittest.main()

0 commit comments

Comments
 (0)