Skip to content

Commit 21414b0

Browse files
committed
feat: single duration formatter + unit tests
1 parent 9ed1d1e commit 21414b0

File tree

4 files changed

+102
-28
lines changed

4 files changed

+102
-28
lines changed

.github/workflows/build.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,52 @@ jobs:
4444
run: |
4545
pre-commit run --all-files --show-diff-on-failure
4646
47+
test-unit:
48+
name: 🧪 Unit tests
49+
strategy:
50+
fail-fast: false
51+
matrix:
52+
python: ["3.9", "3.10", "3.11", "3.12", "3.13"]
53+
runs-on: ubuntu-latest
54+
steps:
55+
- uses: actions/checkout@v4
56+
- name: 🏗 Set up Python ${{ matrix.python }}
57+
uses: actions/setup-python@v4
58+
with:
59+
python-version: ${{ matrix.python }}
60+
- name: 🏗 Set up test dependencies
61+
run: |
62+
pip install octoprint
63+
pip install -e .[develop]
64+
- name: 🚀 Run test suite
65+
run: |
66+
pytest | tee report.txt
67+
pytest_exit=${PIPESTATUS[0]}
68+
69+
# generate summary
70+
python=$(python -c 'import sys; print(".".join(map(str, sys.version_info[:3])))')
71+
today=$(date +'%Y-%m-%d')
72+
now=$(date +'%H:%M')
73+
summary=$(tail -n1 report.txt | sed 's/^=*\s//g' | sed 's/\s=*$//g')
74+
75+
cat << EOF >> $GITHUB_STEP_SUMMARY
76+
### Test Report
77+
78+
*generated on $today at $now under Python $python*
79+
80+
<details>
81+
<summary>$summary</summary>
82+
83+
\`\`\`
84+
$(cat report.txt)
85+
\`\`\`
86+
87+
</details>
88+
EOF
89+
90+
# make sure we fail if pytest fails!
91+
exit $pytest_exit
92+
4793
test-install:
4894
name: 🧪 Installation test
4995
strategy:
@@ -92,6 +138,7 @@ jobs:
92138
needs:
93139
- build
94140
- pre-commit
141+
- test-unit
95142
- test-install
96143
- test-e2e
97144
runs-on: ubuntu-latest

octoprint_wrapped/__init__.py

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -215,10 +215,10 @@ def _get_year_stats(self, year: int) -> Optional[YearStats]:
215215
return YearStats(
216216
year=year,
217217
prints_completed=stats.get("prints_finished", 0),
218-
total_print_duration=self._to_duration_days(
218+
total_print_duration=_format_duration(
219219
int(stats.get("print_duration_total", 0))
220220
),
221-
longest_print=self._to_duration_hours(
221+
longest_print=_format_duration(
222222
int(stats.get("longest_print_duration", 0))
223223
),
224224
busiest_weekday=weekday,
@@ -231,31 +231,6 @@ def _get_year_stats(self, year: int) -> Optional[YearStats]:
231231
)
232232
return None
233233

234-
def _to_duration_days(self, seconds: int) -> str:
235-
days = int(seconds / SECONDS_DAY)
236-
seconds -= days * SECONDS_DAY
237-
238-
hours = int(seconds / SECONDS_HOUR)
239-
seconds -= hours * SECONDS_HOUR
240-
241-
minutes = int(seconds / SECONDS_MINUTE)
242-
seconds -= minutes * SECONDS_MINUTE
243-
244-
if days >= 100:
245-
# strip the minutes to keep things fitting...
246-
return f"{days}d {hours}h"
247-
else:
248-
return f"{days}d {hours}h {minutes}m"
249-
250-
def _to_duration_hours(self, seconds: int) -> str:
251-
hours = int(seconds / SECONDS_HOUR)
252-
seconds -= hours * SECONDS_HOUR
253-
254-
minutes = int(seconds / SECONDS_MINUTE)
255-
seconds -= minutes * SECONDS_MINUTE
256-
257-
return f"{hours}h {minutes}m"
258-
259234
def _load_font(self) -> None:
260235
from base64 import b64encode
261236

@@ -273,6 +248,54 @@ def _load_font(self) -> None:
273248
self._logger.exception("Error creating data URI for embedded font")
274249

275250

251+
def _format_duration(seconds: int) -> str:
252+
"""
253+
Formats a duration in seconds for the wrapped picture.
254+
255+
Examples:
256+
257+
>>> _format_duration(SECONDS_MINUTE)
258+
'0h 1m'
259+
>>> _format_duration(SECONDS_HOUR)
260+
'1h 0m'
261+
>>> _format_duration(SECONDS_DAY)
262+
'1d 0h 0m'
263+
>>> _format_duration(SECONDS_DAY + SECONDS_HOUR + SECONDS_MINUTE)
264+
'1d 1h 1m'
265+
>>> _format_duration(99 * SECONDS_DAY + SECONDS_HOUR + SECONDS_MINUTE)
266+
'99d 1h 1m'
267+
>>> _format_duration(100 * SECONDS_DAY + SECONDS_HOUR + SECONDS_MINUTE)
268+
'100d 1h'
269+
>>> _format_duration(100 * SECONDS_DAY + SECONDS_HOUR + 30 * SECONDS_MINUTE)
270+
'100d 2h'
271+
>>> _format_duration(100 * SECONDS_DAY + 23 * SECONDS_HOUR + 30 * SECONDS_MINUTE)
272+
'101d 0h'
273+
"""
274+
days = int(seconds / SECONDS_DAY)
275+
seconds %= SECONDS_DAY
276+
277+
hours = int(seconds / SECONDS_HOUR)
278+
seconds %= SECONDS_HOUR
279+
280+
minutes = int(seconds / SECONDS_MINUTE)
281+
seconds %= SECONDS_MINUTE
282+
283+
if days >= 100:
284+
# strip the minutes to keep things fitting (but round up if needed)...
285+
if minutes >= 30:
286+
# past 30m -> add an hour
287+
hours += 1
288+
if hours >= 24:
289+
# past 24h -> add a day
290+
hours %= 24
291+
days += 1
292+
return f"{days}d {hours}h"
293+
elif days == 0:
294+
return f"{hours}h {minutes}m"
295+
else:
296+
return f"{days}d {hours}h {minutes}m"
297+
298+
276299
__plugin_name__ = "OctoPrint Wrapped!"
277300
__plugin_pythoncompat__ = ">=3.9,<4" # Only Python 3
278301

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ Homepage = "https://github.com/OctoPrint/OctoPrint-Wrapped"
3636

3737
[project.optional-dependencies]
3838
develop = [
39-
"go-task-bin"
39+
"go-task-bin",
40+
"pytest"
4041
]
4142

4243
[tool.ruff]

pytest.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[pytest]
2+
testpaths = octoprint_wrapped tests
3+
addopts = --doctest-modules

0 commit comments

Comments
 (0)