Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->

## Types of changes
## Type of change:
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Refactoring / documentation update (non-breaking change which does not change functionality)
- [ ] Bug fix (non-breaking change which fixes an issue)
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## v1.x.y (development branch)

- Prepare for v1.1.0 development ([\#44](https://github.com/ubccr/xdmod-data/pull/44)).
- Implement 100% test coverage ([\#](https://github.com/ubccr/xdmod-data/pull/)).

## v1.0.2 (2024-10-31)

Expand Down
22 changes: 18 additions & 4 deletions docs/developing.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
```
python3 -m pip install --force-reinstall -e /path/to/your/xdmod-data
```
1. Install `python-dotenv` and `pytest`:
1. Install `python-dotenv`, `pytest`, and `coverage`:
```
python3 -m pip install --upgrade python-dotenv pytest
python3 -m pip install --upgrade python-dotenv pytest coverage
```
1. Create an empty file in your home directory at `~/.xdmod-data-token` with permissions set to 600.
1. With an [ACCESS XDMoD](https://xdmod.access-ci.org) account with "User" as the Top Role, create an API token if you do not already have one (sign in and click My Profile -> API Token).
Expand All @@ -24,15 +24,23 @@
1. Change directories to your local development copy of `xdmod-data`.
1. Run the following command and make sure all the tests pass:
```
pytest -vvs -o log_cli=true tests/
coverage run -m pytest -vvs -o log_cli=true tests/
```
1. Run the following command and make sure the code is 100% covered by tests:
```
coverage report -m
```
1. Downgrade to the minimum version of the dependencies. Replace the version numbers below with the values from `setup.cfg`.
```
python3 -m pip install --force-reinstall numpy==1.23.0 pandas==1.5.0 plotly==5.8.0 requests==2.19.0
```
1. Run the following command again and make sure all the tests pass (Deprecation warnings in `urllib3` are OK).
```
pytest -vvs -o log_cli=true tests/
coverage run -m pytest -vvs -o log_cli=true tests/
```
1. Run the following command and make sure the code is 100% covered by tests:
```
coverage report -m
```

## Releasing a new version
Expand Down Expand Up @@ -137,3 +145,9 @@
under the `on` section.
1. Go to the [GitHub milestones](https://github.com/ubccr/xdmod-data/milestones)
and add a milestone for the version under development.
1. Make a new branch of `xdmod-data` and:
1. Make sure the version number is updated in `xdmod_data/__version__.py` to a pre-release of the next version, e.g., `1.0.1-01`.
1. Update `CHANGELOG.md` to add a section at the top called `Main development branch`.
1. Go to the [GitHub milestones](https://github.com/ubccr/xdmod-data/milestones) and add a milestone for the version.
1. Create a Pull Request for the new version.
1. Once the Pull Request is approved, merge it into `main`.
1 change: 1 addition & 0 deletions tests/integration/test_datawarehouse_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,7 @@ def test_case_insensitive(dw_methods, method, param, value1, value2):
'dw_methods,method',
[(VALID_XDMOD_URL + '/', method) for method in list(METHOD_PARAMS.keys())],
indirect=['dw_methods'],
ids=[method for method in list(METHOD_PARAMS.keys())],
)
def test_trailing_slashes(dw_methods, method):
__run_method(dw_methods, method)
3 changes: 3 additions & 0 deletions tests/regression/data/jobs-2022-2023-years.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Time,CPU Hours: Total
2022-01-01,2537282552.1619
2023-01-01,2929521819.2386
5 changes: 5 additions & 0 deletions tests/regression/data/jobs-2023-quarters.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Time,CPU Hours: Total
2023-01-01,880429724.9542
2023-04-01,806424829.2281
2023-07-01,621011929.8592
2023-10-01,621655335.1972
67 changes: 66 additions & 1 deletion tests/regression/test_datawarehouse_regression.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from datetime import date
from dotenv import load_dotenv
import numpy
from os.path import dirname, expanduser
Expand Down Expand Up @@ -25,6 +26,7 @@ def __assert_dfs_equal(
actual,
dtype='object',
index_col='id',
columns_name=None,
):
expected = pandas.read_csv(
DATA_DIR + '/' + data_file,
Expand All @@ -34,10 +36,13 @@ def __assert_dfs_equal(
na_values=[''],
).fillna(numpy.nan)
expected.columns = expected.columns.astype('string')
expected.columns.name = columns_name
if index_col == 'Time':
expected.index = pandas.to_datetime(expected.index)
assert expected.equals(actual)


def test_get_raw_data(valid_dw):
def test_get_raw_data(valid_dw, capsys):
data = valid_dw.get_raw_data(
duration=('2023-05-01', '2023-05-02'),
realm='SUPREMM',
Expand All @@ -57,6 +62,7 @@ def test_get_raw_data(valid_dw):
'Bridges 2 RM',
],
},
show_progress=True,
).iloc[::1000]
data.index = data.index.astype('string')
__assert_dfs_equal(
Expand All @@ -65,6 +71,7 @@ def test_get_raw_data(valid_dw):
dtype='string',
index_col=0,
)
assert 'Got 42240 rows...DONE' in capsys.readouterr().out


def __assert_descriptor_dfs_equal(data_file, actual):
Expand Down Expand Up @@ -109,3 +116,61 @@ def test_get_data_filter_user(valid_dw):
dataset_type='aggregate',
filters={'User': '10332'},
)


@pytest.mark.parametrize(
'duration,aggregation_unit,data_file',
[
(('2023-01-01', '2023-12-31'), 'Quarter', 'jobs-2023-quarters.csv'),
(('2022-01-01', '2023-12-31'), 'Year', 'jobs-2022-2023-years.csv'),
],
ids=('quarter', 'year'),
)
def test_get_data(valid_dw, duration, aggregation_unit, data_file):
data = valid_dw.get_data(
duration=duration,
realm='Jobs',
metric='CPU Hours: Total',
aggregation_unit=aggregation_unit,
)
__assert_dfs_equal(
data_file,
data,
index_col='Time',
columns_name='Metric',
dtype={'CPU Hours: Total': 'Float64'},
)


def test_get_aggregation_units(valid_dw):
expected_agg_units = ('Auto', 'Day', 'Month', 'Quarter', 'Year')
actual_agg_units = valid_dw.get_aggregation_units()
assert expected_agg_units == actual_agg_units


def test_get_durations(valid_dw):
expected_durations = [
'Yesterday',
'7 day',
'30 day',
'90 day',
'Month to date',
'Previous month',
'Quarter to date',
'Previous quarter',
'Year to date',
'Previous year',
'1 year',
'2 year',
'3 year',
'5 year',
'10 year',
]
today_date = date.today()
current_year = today_date.year
for count in range(0, 7):
year = current_year - count
year = str(year)
expected_durations.append(year)
actual_durations = list(valid_dw.get_durations())
assert expected_durations == actual_durations
6 changes: 3 additions & 3 deletions tests/unit/test_datawarehouse_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,14 @@ def test___enter___RuntimeError_xdmod_host_malformed():
)
):
with DataWarehouse('https://'):
pass
pass # pragma: no cover


def test___enter___RuntimeError_xdmod_host_unresolved():
invalid_host = 'https://' + INVALID_STR + '.xdmod.org'
with pytest.raises(Exception):
with DataWarehouse(invalid_host):
pass
pass # pragma: no cover


def test___enter___RuntimeError_xdmod_host_unsupported_protocol():
Expand All @@ -67,7 +67,7 @@ def test___enter___RuntimeError_xdmod_host_unsupported_protocol():
match='No connection adapters were found for \'' + invalid_host
):
with DataWarehouse(invalid_host):
pass
pass # pragma: no cover


def test___enter___RuntimeError_401():
Expand Down
2 changes: 1 addition & 1 deletion xdmod_data/__version__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
__title__ = 'xdmod-data'
__version__ = '1.1.0.dev1'
__version__ = '1.1.0-02'
2 changes: 1 addition & 1 deletion xdmod_data/_descriptors.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def __request_aggregate(self):
'/controllers/metric_explorer.php',
{'operation': 'get_dw_descripter'},
)
if response['totalCount'] != 1:
if response['totalCount'] != 1: # pragma: no cover
raise RuntimeError(
'Descriptor received with unexpected structure.'
)
Expand Down
16 changes: 8 additions & 8 deletions xdmod_data/_http_requester.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def _request_raw_data(self, params):
i += 1
if params['show_progress']:
self.__print_progress_msg(i, 'DONE\n')
else:
else: # pragma: no cover
num_rows = limit
offset = 0
while num_rows == limit:
Expand Down Expand Up @@ -115,7 +115,7 @@ def _request_json(self, path, post_fields=None):
def __assert_connection_to_xdmod_host(self):
try:
self.__request()
except RuntimeError as e:
except RuntimeError as e: # pragma: no cover
raise RuntimeError(
'Could not connect to xdmod_host \'' + self.__xdmod_host
+ '\': ' + str(e)
Expand All @@ -140,7 +140,7 @@ def __request(self, path='', post_fields=None, stream=False):
try:
response_json = json.loads(response.text)
msg = ': ' + response_json['message']
except json.JSONDecodeError:
except json.JSONDecodeError: # pragma: no cover
pass
if response.status_code == 401:
msg = (
Expand All @@ -151,7 +151,7 @@ def __request(self, path='', post_fields=None, stream=False):
) from None
if stream:
return response.iter_lines()
else:
else: # pragma: no cover
return response.text

def __get_data_post_fields(self, params):
Expand Down Expand Up @@ -187,19 +187,19 @@ def __get_raw_data_url_params(self, params):
)
return urlencode(results)

# Once XDMoD 10.5 is no longer supported, there will be no need for this
# method.
# Once XDMoD 10.5 is no longer supported,
# there will be no need for this method.
def __get_raw_data_limit(self):
if self.__raw_data_limit is None:
try:
response = self._request_json(
'/rest/v1/warehouse/raw-data/limit'
)
self.__raw_data_limit = int(response['data'])
self.__raw_data_limit = int(response['data']) # pragma: no cover
except RuntimeError as e:
if '404' in str(e):
self.__raw_data_limit = 'NA'
else:
else: # pragma: no cover
raise
return self.__raw_data_limit

Expand Down
9 changes: 3 additions & 6 deletions xdmod_data/_response_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,7 @@ def __parse_timeseries_dimension_values(labels):
dimension_values = []
for label in labels:
match = label_re.match(label)
if match:
dimension_values.append(html.unescape(match.group(1)))
else:
dimension_values.append(html.unescape(label))
dimension_values.append(html.unescape(match.group(1)))
return dimension_values


Expand All @@ -75,7 +72,7 @@ def __parse_timeseries_date_string(date_string):
# Match YYYY Q#
elif re.match(r'^[0-9]{4} Q[0-9]$', date_string):
(date_string, format_) = __parse_quarter_date_string(date_string)
else:
else: # pragma: no cover
raise Exception(
'Unsupported date specification ' + date_string + '.'
)
Expand Down Expand Up @@ -134,7 +131,7 @@ def __parse_quarter_date_string(date_string):
month = '07'
elif quarter == 'Q4':
month = '10'
else:
else: # pragma: no cover
raise Exception(
'Unsupported date quarter specification '
+ date_string + '.'
Expand Down
10 changes: 5 additions & 5 deletions xdmod_data/_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,10 +254,10 @@ def __get_dates_from_duration(duration):
last_month = today + timedelta(days=-30)
last_quarter = today + timedelta(days=-90)
this_month_start = date(today.year, today.month, 1)
if today.month == 1:
if today.month == 1: # pragma: no cover
last_full_month_start_year = today.year - 1
last_full_month_start_month = 12
else:
else: # pragma: no cover
last_full_month_start_year = today.year
last_full_month_start_month = today.month - 1
last_full_month_start = date(
Expand All @@ -271,9 +271,9 @@ def __get_dates_from_duration(duration):
((today.month - 1) // 3) * 3 + 1,
1,
)
if today.month < 4:
if today.month < 4: # pragma: no cover
last_quarter_start_year = today.year - 1
else:
else: # pragma: no cover
last_quarter_start_year = today.year
last_quarter_start = date(
last_quarter_start_year,
Expand Down Expand Up @@ -340,7 +340,7 @@ def __date_add_years(old_date, year_delta):
try:
new_date = date(new_date_year, old_date.month, new_date_day)
keep_going = False
except ValueError:
except ValueError: # pragma: no cover
new_date_day -= 1
days_above += 1
return new_date + timedelta(days=days_above)